Merge remote-tracking branch 'origin/test'

# Conflicts:
#	services/gateway-service/internal/transport/http/response_test.go
This commit is contained in:
zhx 2026-06-08 21:46:41 +08:00
commit 86ec305448
119 changed files with 20669 additions and 20156 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1187,6 +1187,27 @@ message ConsumeLevelEventResponse {
int64 reward_job_count = 6;
}
message SetUserLevelRequest {
RequestMeta meta = 1;
string command_id = 2;
int64 user_id = 3;
string track = 4;
int32 level = 5;
int64 operator_user_id = 6;
string reason = 7;
}
message SetUserLevelResponse {
string event_id = 1;
string status = 2;
string track = 3;
int32 previous_level = 4;
int32 new_level = 5;
int64 total_value = 6;
int64 reward_job_count = 7;
int64 server_time_ms = 8;
}
message RegistrationLevelBadgeGrant {
string track = 1;
int32 level = 2;
@ -1844,6 +1865,7 @@ service GrowthLevelService {
rpc BatchGetUserLevelDisplayProfiles(BatchGetUserLevelDisplayProfilesRequest) returns (BatchGetUserLevelDisplayProfilesResponse);
rpc ListLevelRewards(ListLevelRewardsRequest) returns (ListLevelRewardsResponse);
rpc ConsumeLevelEvent(ConsumeLevelEventRequest) returns (ConsumeLevelEventResponse);
rpc SetUserLevel(SetUserLevelRequest) returns (SetUserLevelResponse);
rpc IssueRegistrationLevelBadges(IssueRegistrationLevelBadgesRequest) returns (IssueRegistrationLevelBadgesResponse);
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/activity/v1/activity.proto
@ -80,10 +80,10 @@ type ActivityServiceServer interface {
type UnimplementedActivityServiceServer struct{}
func (UnimplementedActivityServiceServer) PingActivity(context.Context, *PingActivityRequest) (*PingActivityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PingActivity not implemented")
return nil, status.Error(codes.Unimplemented, "method PingActivity not implemented")
}
func (UnimplementedActivityServiceServer) GetActivityStatus(context.Context, *GetActivityStatusRequest) (*GetActivityStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetActivityStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method GetActivityStatus not implemented")
}
func (UnimplementedActivityServiceServer) mustEmbedUnimplementedActivityServiceServer() {}
func (UnimplementedActivityServiceServer) testEmbeddedByValue() {}
@ -96,7 +96,7 @@ type UnsafeActivityServiceServer interface {
}
func RegisterActivityServiceServer(s grpc.ServiceRegistrar, srv ActivityServiceServer) {
// If the following call pancis, it indicates UnimplementedActivityServiceServer was
// If the following call panics, it indicates UnimplementedActivityServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -289,25 +289,25 @@ type MessageInboxServiceServer interface {
type UnimplementedMessageInboxServiceServer struct{}
func (UnimplementedMessageInboxServiceServer) ListMessageTabs(context.Context, *ListMessageTabsRequest) (*ListMessageTabsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMessageTabs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListMessageTabs not implemented")
}
func (UnimplementedMessageInboxServiceServer) ListInboxMessages(context.Context, *ListInboxMessagesRequest) (*ListInboxMessagesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListInboxMessages not implemented")
return nil, status.Error(codes.Unimplemented, "method ListInboxMessages not implemented")
}
func (UnimplementedMessageInboxServiceServer) MarkInboxMessageRead(context.Context, *MarkInboxMessageReadRequest) (*MarkInboxMessageReadResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MarkInboxMessageRead not implemented")
return nil, status.Error(codes.Unimplemented, "method MarkInboxMessageRead not implemented")
}
func (UnimplementedMessageInboxServiceServer) MarkInboxSectionRead(context.Context, *MarkInboxSectionReadRequest) (*MarkInboxSectionReadResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MarkInboxSectionRead not implemented")
return nil, status.Error(codes.Unimplemented, "method MarkInboxSectionRead not implemented")
}
func (UnimplementedMessageInboxServiceServer) DeleteInboxMessage(context.Context, *DeleteInboxMessageRequest) (*DeleteInboxMessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteInboxMessage not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteInboxMessage not implemented")
}
func (UnimplementedMessageInboxServiceServer) CreateInboxMessage(context.Context, *CreateInboxMessageRequest) (*CreateInboxMessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateInboxMessage not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateInboxMessage not implemented")
}
func (UnimplementedMessageInboxServiceServer) CreateFanoutJob(context.Context, *CreateFanoutJobRequest) (*CreateFanoutJobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateFanoutJob not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateFanoutJob not implemented")
}
func (UnimplementedMessageInboxServiceServer) mustEmbedUnimplementedMessageInboxServiceServer() {}
func (UnimplementedMessageInboxServiceServer) testEmbeddedByValue() {}
@ -320,7 +320,7 @@ type UnsafeMessageInboxServiceServer interface {
}
func RegisterMessageInboxServiceServer(s grpc.ServiceRegistrar, srv MessageInboxServiceServer) {
// If the following call pancis, it indicates UnimplementedMessageInboxServiceServer was
// If the following call panics, it indicates UnimplementedMessageInboxServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -597,19 +597,19 @@ type ActivityCronServiceServer interface {
type UnimplementedActivityCronServiceServer struct{}
func (UnimplementedActivityCronServiceServer) ProcessMessageFanoutBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessMessageFanoutBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessMessageFanoutBatch not implemented")
}
func (UnimplementedActivityCronServiceServer) ProcessLevelRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessLevelRewardBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessLevelRewardBatch not implemented")
}
func (UnimplementedActivityCronServiceServer) ProcessAchievementRewardBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessAchievementRewardBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessAchievementRewardBatch not implemented")
}
func (UnimplementedActivityCronServiceServer) ProcessRoomTurnoverRewardSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessRoomTurnoverRewardSettlementBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessRoomTurnoverRewardSettlementBatch not implemented")
}
func (UnimplementedActivityCronServiceServer) ProcessWeeklyStarSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessWeeklyStarSettlementBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessWeeklyStarSettlementBatch not implemented")
}
func (UnimplementedActivityCronServiceServer) mustEmbedUnimplementedActivityCronServiceServer() {}
func (UnimplementedActivityCronServiceServer) testEmbeddedByValue() {}
@ -622,7 +622,7 @@ type UnsafeActivityCronServiceServer interface {
}
func RegisterActivityCronServiceServer(s grpc.ServiceRegistrar, srv ActivityCronServiceServer) {
// If the following call pancis, it indicates UnimplementedActivityCronServiceServer was
// If the following call panics, it indicates UnimplementedActivityCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -829,13 +829,13 @@ type TaskServiceServer interface {
type UnimplementedTaskServiceServer struct{}
func (UnimplementedTaskServiceServer) ListUserTasks(context.Context, *ListUserTasksRequest) (*ListUserTasksResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserTasks not implemented")
return nil, status.Error(codes.Unimplemented, "method ListUserTasks not implemented")
}
func (UnimplementedTaskServiceServer) ClaimTaskReward(context.Context, *ClaimTaskRewardRequest) (*ClaimTaskRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimTaskReward not implemented")
return nil, status.Error(codes.Unimplemented, "method ClaimTaskReward not implemented")
}
func (UnimplementedTaskServiceServer) ConsumeTaskEvent(context.Context, *ConsumeTaskEventRequest) (*ConsumeTaskEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeTaskEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeTaskEvent not implemented")
}
func (UnimplementedTaskServiceServer) mustEmbedUnimplementedTaskServiceServer() {}
func (UnimplementedTaskServiceServer) testEmbeddedByValue() {}
@ -848,7 +848,7 @@ type UnsafeTaskServiceServer interface {
}
func RegisterTaskServiceServer(s grpc.ServiceRegistrar, srv TaskServiceServer) {
// If the following call pancis, it indicates UnimplementedTaskServiceServer was
// If the following call panics, it indicates UnimplementedTaskServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -942,6 +942,7 @@ const (
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_SetUserLevel_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/SetUserLevel"
GrowthLevelService_IssueRegistrationLevelBadges_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/IssueRegistrationLevelBadges"
)
@ -956,6 +957,7 @@ type GrowthLevelServiceClient interface {
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)
SetUserLevel(ctx context.Context, in *SetUserLevelRequest, opts ...grpc.CallOption) (*SetUserLevelResponse, error)
IssueRegistrationLevelBadges(ctx context.Context, in *IssueRegistrationLevelBadgesRequest, opts ...grpc.CallOption) (*IssueRegistrationLevelBadgesResponse, error)
}
@ -1017,6 +1019,16 @@ func (c *growthLevelServiceClient) ConsumeLevelEvent(ctx context.Context, in *Co
return out, nil
}
func (c *growthLevelServiceClient) SetUserLevel(ctx context.Context, in *SetUserLevelRequest, opts ...grpc.CallOption) (*SetUserLevelResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SetUserLevelResponse)
err := c.cc.Invoke(ctx, GrowthLevelService_SetUserLevel_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
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)
@ -1038,6 +1050,7 @@ type GrowthLevelServiceServer interface {
BatchGetUserLevelDisplayProfiles(context.Context, *BatchGetUserLevelDisplayProfilesRequest) (*BatchGetUserLevelDisplayProfilesResponse, error)
ListLevelRewards(context.Context, *ListLevelRewardsRequest) (*ListLevelRewardsResponse, error)
ConsumeLevelEvent(context.Context, *ConsumeLevelEventRequest) (*ConsumeLevelEventResponse, error)
SetUserLevel(context.Context, *SetUserLevelRequest) (*SetUserLevelResponse, error)
IssueRegistrationLevelBadges(context.Context, *IssueRegistrationLevelBadgesRequest) (*IssueRegistrationLevelBadgesResponse, error)
mustEmbedUnimplementedGrowthLevelServiceServer()
}
@ -1050,22 +1063,25 @@ type GrowthLevelServiceServer interface {
type UnimplementedGrowthLevelServiceServer struct{}
func (UnimplementedGrowthLevelServiceServer) GetMyLevelOverview(context.Context, *GetMyLevelOverviewRequest) (*GetMyLevelOverviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyLevelOverview not implemented")
return nil, status.Error(codes.Unimplemented, "method GetMyLevelOverview not implemented")
}
func (UnimplementedGrowthLevelServiceServer) GetLevelTrack(context.Context, *GetLevelTrackRequest) (*GetLevelTrackResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLevelTrack not implemented")
return nil, status.Error(codes.Unimplemented, "method GetLevelTrack not implemented")
}
func (UnimplementedGrowthLevelServiceServer) BatchGetUserLevelDisplayProfiles(context.Context, *BatchGetUserLevelDisplayProfilesRequest) (*BatchGetUserLevelDisplayProfilesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserLevelDisplayProfiles not implemented")
return nil, status.Error(codes.Unimplemented, "method BatchGetUserLevelDisplayProfiles not implemented")
}
func (UnimplementedGrowthLevelServiceServer) ListLevelRewards(context.Context, *ListLevelRewardsRequest) (*ListLevelRewardsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLevelRewards not implemented")
return nil, status.Error(codes.Unimplemented, "method ListLevelRewards not implemented")
}
func (UnimplementedGrowthLevelServiceServer) ConsumeLevelEvent(context.Context, *ConsumeLevelEventRequest) (*ConsumeLevelEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeLevelEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeLevelEvent not implemented")
}
func (UnimplementedGrowthLevelServiceServer) SetUserLevel(context.Context, *SetUserLevelRequest) (*SetUserLevelResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetUserLevel not implemented")
}
func (UnimplementedGrowthLevelServiceServer) IssueRegistrationLevelBadges(context.Context, *IssueRegistrationLevelBadgesRequest) (*IssueRegistrationLevelBadgesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IssueRegistrationLevelBadges not implemented")
return nil, status.Error(codes.Unimplemented, "method IssueRegistrationLevelBadges not implemented")
}
func (UnimplementedGrowthLevelServiceServer) mustEmbedUnimplementedGrowthLevelServiceServer() {}
func (UnimplementedGrowthLevelServiceServer) testEmbeddedByValue() {}
@ -1078,7 +1094,7 @@ type UnsafeGrowthLevelServiceServer interface {
}
func RegisterGrowthLevelServiceServer(s grpc.ServiceRegistrar, srv GrowthLevelServiceServer) {
// If the following call pancis, it indicates UnimplementedGrowthLevelServiceServer was
// If the following call panics, it indicates UnimplementedGrowthLevelServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1178,6 +1194,24 @@ func _GrowthLevelService_ConsumeLevelEvent_Handler(srv interface{}, ctx context.
return interceptor(ctx, in, info, handler)
}
func _GrowthLevelService_SetUserLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetUserLevelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GrowthLevelServiceServer).SetUserLevel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: GrowthLevelService_SetUserLevel_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GrowthLevelServiceServer).SetUserLevel(ctx, req.(*SetUserLevelRequest))
}
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 {
@ -1223,6 +1257,10 @@ var GrowthLevelService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ConsumeLevelEvent",
Handler: _GrowthLevelService_ConsumeLevelEvent_Handler,
},
{
MethodName: "SetUserLevel",
Handler: _GrowthLevelService_SetUserLevel_Handler,
},
{
MethodName: "IssueRegistrationLevelBadges",
Handler: _GrowthLevelService_IssueRegistrationLevelBadges_Handler,
@ -1320,16 +1358,16 @@ type AchievementServiceServer interface {
type UnimplementedAchievementServiceServer struct{}
func (UnimplementedAchievementServiceServer) ListAchievements(context.Context, *ListAchievementsRequest) (*ListAchievementsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAchievements not implemented")
return nil, status.Error(codes.Unimplemented, "method ListAchievements not implemented")
}
func (UnimplementedAchievementServiceServer) ConsumeAchievementEvent(context.Context, *ConsumeAchievementEventRequest) (*ConsumeAchievementEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeAchievementEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeAchievementEvent not implemented")
}
func (UnimplementedAchievementServiceServer) ListMyBadges(context.Context, *ListMyBadgesRequest) (*ListMyBadgesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMyBadges not implemented")
return nil, status.Error(codes.Unimplemented, "method ListMyBadges not implemented")
}
func (UnimplementedAchievementServiceServer) SetBadgeDisplay(context.Context, *SetBadgeDisplayRequest) (*SetBadgeDisplayResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetBadgeDisplay not implemented")
return nil, status.Error(codes.Unimplemented, "method SetBadgeDisplay not implemented")
}
func (UnimplementedAchievementServiceServer) mustEmbedUnimplementedAchievementServiceServer() {}
func (UnimplementedAchievementServiceServer) testEmbeddedByValue() {}
@ -1342,7 +1380,7 @@ type UnsafeAchievementServiceServer interface {
}
func RegisterAchievementServiceServer(s grpc.ServiceRegistrar, srv AchievementServiceServer) {
// If the following call pancis, it indicates UnimplementedAchievementServiceServer was
// If the following call panics, it indicates UnimplementedAchievementServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1514,10 +1552,10 @@ type LuckyGiftServiceServer interface {
type UnimplementedLuckyGiftServiceServer struct{}
func (UnimplementedLuckyGiftServiceServer) CheckLuckyGift(context.Context, *CheckLuckyGiftRequest) (*CheckLuckyGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckLuckyGift not implemented")
return nil, status.Error(codes.Unimplemented, "method CheckLuckyGift not implemented")
}
func (UnimplementedLuckyGiftServiceServer) ExecuteLuckyGiftDraw(context.Context, *ExecuteLuckyGiftDrawRequest) (*ExecuteLuckyGiftDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExecuteLuckyGiftDraw not implemented")
return nil, status.Error(codes.Unimplemented, "method ExecuteLuckyGiftDraw not implemented")
}
func (UnimplementedLuckyGiftServiceServer) mustEmbedUnimplementedLuckyGiftServiceServer() {}
func (UnimplementedLuckyGiftServiceServer) testEmbeddedByValue() {}
@ -1530,7 +1568,7 @@ type UnsafeLuckyGiftServiceServer interface {
}
func RegisterLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv LuckyGiftServiceServer) {
// If the following call pancis, it indicates UnimplementedLuckyGiftServiceServer was
// If the following call panics, it indicates UnimplementedLuckyGiftServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1645,7 +1683,7 @@ type RoomTurnoverRewardServiceServer interface {
type UnimplementedRoomTurnoverRewardServiceServer struct{}
func (UnimplementedRoomTurnoverRewardServiceServer) GetRoomTurnoverRewardStatus(context.Context, *GetRoomTurnoverRewardStatusRequest) (*GetRoomTurnoverRewardStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomTurnoverRewardStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRoomTurnoverRewardStatus not implemented")
}
func (UnimplementedRoomTurnoverRewardServiceServer) mustEmbedUnimplementedRoomTurnoverRewardServiceServer() {
}
@ -1659,7 +1697,7 @@ type UnsafeRoomTurnoverRewardServiceServer interface {
}
func RegisterRoomTurnoverRewardServiceServer(s grpc.ServiceRegistrar, srv RoomTurnoverRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomTurnoverRewardServiceServer was
// If the following call panics, it indicates UnimplementedRoomTurnoverRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1778,13 +1816,13 @@ type WeeklyStarServiceServer interface {
type UnimplementedWeeklyStarServiceServer struct{}
func (UnimplementedWeeklyStarServiceServer) GetWeeklyStarCurrent(context.Context, *GetWeeklyStarCurrentRequest) (*GetWeeklyStarCurrentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWeeklyStarCurrent not implemented")
return nil, status.Error(codes.Unimplemented, "method GetWeeklyStarCurrent not implemented")
}
func (UnimplementedWeeklyStarServiceServer) ListWeeklyStarLeaderboard(context.Context, *ListWeeklyStarLeaderboardRequest) (*ListWeeklyStarLeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWeeklyStarLeaderboard not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWeeklyStarLeaderboard not implemented")
}
func (UnimplementedWeeklyStarServiceServer) ListWeeklyStarHistory(context.Context, *ListWeeklyStarHistoryRequest) (*ListWeeklyStarHistoryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWeeklyStarHistory not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWeeklyStarHistory not implemented")
}
func (UnimplementedWeeklyStarServiceServer) mustEmbedUnimplementedWeeklyStarServiceServer() {}
func (UnimplementedWeeklyStarServiceServer) testEmbeddedByValue() {}
@ -1797,7 +1835,7 @@ type UnsafeWeeklyStarServiceServer interface {
}
func RegisterWeeklyStarServiceServer(s grpc.ServiceRegistrar, srv WeeklyStarServiceServer) {
// If the following call pancis, it indicates UnimplementedWeeklyStarServiceServer was
// If the following call panics, it indicates UnimplementedWeeklyStarServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1986,19 +2024,19 @@ type BroadcastServiceServer interface {
type UnimplementedBroadcastServiceServer struct{}
func (UnimplementedBroadcastServiceServer) EnsureBroadcastGroups(context.Context, *EnsureBroadcastGroupsRequest) (*EnsureBroadcastGroupsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EnsureBroadcastGroups not implemented")
return nil, status.Error(codes.Unimplemented, "method EnsureBroadcastGroups not implemented")
}
func (UnimplementedBroadcastServiceServer) PublishRegionBroadcast(context.Context, *PublishRegionBroadcastRequest) (*PublishBroadcastResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PublishRegionBroadcast not implemented")
return nil, status.Error(codes.Unimplemented, "method PublishRegionBroadcast not implemented")
}
func (UnimplementedBroadcastServiceServer) PublishGlobalBroadcast(context.Context, *PublishGlobalBroadcastRequest) (*PublishBroadcastResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PublishGlobalBroadcast not implemented")
return nil, status.Error(codes.Unimplemented, "method PublishGlobalBroadcast not implemented")
}
func (UnimplementedBroadcastServiceServer) RemoveRegionBroadcastMember(context.Context, *RemoveRegionBroadcastMemberRequest) (*RemoveRegionBroadcastMemberResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveRegionBroadcastMember not implemented")
return nil, status.Error(codes.Unimplemented, "method RemoveRegionBroadcastMember not implemented")
}
func (UnimplementedBroadcastServiceServer) ProcessBroadcastOutboxBatch(context.Context, *CronBatchRequest) (*ProcessBroadcastOutboxBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessBroadcastOutboxBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessBroadcastOutboxBatch not implemented")
}
func (UnimplementedBroadcastServiceServer) mustEmbedUnimplementedBroadcastServiceServer() {}
func (UnimplementedBroadcastServiceServer) testEmbeddedByValue() {}
@ -2011,7 +2049,7 @@ type UnsafeBroadcastServiceServer interface {
}
func RegisterBroadcastServiceServer(s grpc.ServiceRegistrar, srv BroadcastServiceServer) {
// If the following call pancis, it indicates UnimplementedBroadcastServiceServer was
// If the following call panics, it indicates UnimplementedBroadcastServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2192,7 +2230,7 @@ type RoomEventConsumerServiceServer interface {
type UnimplementedRoomEventConsumerServiceServer struct{}
func (UnimplementedRoomEventConsumerServiceServer) ConsumeRoomEvent(context.Context, *ConsumeRoomEventRequest) (*ConsumeRoomEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeRoomEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeRoomEvent not implemented")
}
func (UnimplementedRoomEventConsumerServiceServer) mustEmbedUnimplementedRoomEventConsumerServiceServer() {
}
@ -2206,7 +2244,7 @@ type UnsafeRoomEventConsumerServiceServer interface {
}
func RegisterRoomEventConsumerServiceServer(s grpc.ServiceRegistrar, srv RoomEventConsumerServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomEventConsumerServiceServer was
// If the following call panics, it indicates UnimplementedRoomEventConsumerServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2325,13 +2363,13 @@ type AdminTaskServiceServer interface {
type UnimplementedAdminTaskServiceServer struct{}
func (UnimplementedAdminTaskServiceServer) ListTaskDefinitions(context.Context, *ListTaskDefinitionsRequest) (*ListTaskDefinitionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListTaskDefinitions not implemented")
return nil, status.Error(codes.Unimplemented, "method ListTaskDefinitions not implemented")
}
func (UnimplementedAdminTaskServiceServer) UpsertTaskDefinition(context.Context, *UpsertTaskDefinitionRequest) (*UpsertTaskDefinitionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertTaskDefinition not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertTaskDefinition not implemented")
}
func (UnimplementedAdminTaskServiceServer) SetTaskDefinitionStatus(context.Context, *SetTaskDefinitionStatusRequest) (*SetTaskDefinitionStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetTaskDefinitionStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetTaskDefinitionStatus not implemented")
}
func (UnimplementedAdminTaskServiceServer) mustEmbedUnimplementedAdminTaskServiceServer() {}
func (UnimplementedAdminTaskServiceServer) testEmbeddedByValue() {}
@ -2344,7 +2382,7 @@ type UnsafeAdminTaskServiceServer interface {
}
func RegisterAdminTaskServiceServer(s grpc.ServiceRegistrar, srv AdminTaskServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminTaskServiceServer was
// If the following call panics, it indicates UnimplementedAdminTaskServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2494,10 +2532,10 @@ type RegistrationRewardServiceServer interface {
type UnimplementedRegistrationRewardServiceServer struct{}
func (UnimplementedRegistrationRewardServiceServer) GetRegistrationRewardEligibility(context.Context, *GetRegistrationRewardEligibilityRequest) (*GetRegistrationRewardEligibilityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRegistrationRewardEligibility not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRegistrationRewardEligibility not implemented")
}
func (UnimplementedRegistrationRewardServiceServer) IssueRegistrationReward(context.Context, *IssueRegistrationRewardRequest) (*IssueRegistrationRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IssueRegistrationReward not implemented")
return nil, status.Error(codes.Unimplemented, "method IssueRegistrationReward not implemented")
}
func (UnimplementedRegistrationRewardServiceServer) mustEmbedUnimplementedRegistrationRewardServiceServer() {
}
@ -2511,7 +2549,7 @@ type UnsafeRegistrationRewardServiceServer interface {
}
func RegisterRegistrationRewardServiceServer(s grpc.ServiceRegistrar, srv RegistrationRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedRegistrationRewardServiceServer was
// If the following call panics, it indicates UnimplementedRegistrationRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2652,13 +2690,13 @@ type AdminRegistrationRewardServiceServer interface {
type UnimplementedAdminRegistrationRewardServiceServer struct{}
func (UnimplementedAdminRegistrationRewardServiceServer) GetRegistrationRewardConfig(context.Context, *GetRegistrationRewardConfigRequest) (*GetRegistrationRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRegistrationRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRegistrationRewardConfig not implemented")
}
func (UnimplementedAdminRegistrationRewardServiceServer) UpdateRegistrationRewardConfig(context.Context, *UpdateRegistrationRewardConfigRequest) (*UpdateRegistrationRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRegistrationRewardConfig not implemented")
}
func (UnimplementedAdminRegistrationRewardServiceServer) ListRegistrationRewardClaims(context.Context, *ListRegistrationRewardClaimsRequest) (*ListRegistrationRewardClaimsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRegistrationRewardClaims not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRegistrationRewardClaims not implemented")
}
func (UnimplementedAdminRegistrationRewardServiceServer) mustEmbedUnimplementedAdminRegistrationRewardServiceServer() {
}
@ -2672,7 +2710,7 @@ type UnsafeAdminRegistrationRewardServiceServer interface {
}
func RegisterAdminRegistrationRewardServiceServer(s grpc.ServiceRegistrar, srv AdminRegistrationRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminRegistrationRewardServiceServer was
// If the following call panics, it indicates UnimplementedAdminRegistrationRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2822,10 +2860,10 @@ type FirstRechargeRewardServiceServer interface {
type UnimplementedFirstRechargeRewardServiceServer struct{}
func (UnimplementedFirstRechargeRewardServiceServer) GetFirstRechargeRewardStatus(context.Context, *GetFirstRechargeRewardStatusRequest) (*GetFirstRechargeRewardStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFirstRechargeRewardStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method GetFirstRechargeRewardStatus not implemented")
}
func (UnimplementedFirstRechargeRewardServiceServer) ConsumeFirstRechargeReward(context.Context, *ConsumeFirstRechargeRewardRequest) (*ConsumeFirstRechargeRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeFirstRechargeReward not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeFirstRechargeReward not implemented")
}
func (UnimplementedFirstRechargeRewardServiceServer) mustEmbedUnimplementedFirstRechargeRewardServiceServer() {
}
@ -2839,7 +2877,7 @@ type UnsafeFirstRechargeRewardServiceServer interface {
}
func RegisterFirstRechargeRewardServiceServer(s grpc.ServiceRegistrar, srv FirstRechargeRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedFirstRechargeRewardServiceServer was
// If the following call panics, it indicates UnimplementedFirstRechargeRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2980,13 +3018,13 @@ type AdminFirstRechargeRewardServiceServer interface {
type UnimplementedAdminFirstRechargeRewardServiceServer struct{}
func (UnimplementedAdminFirstRechargeRewardServiceServer) GetFirstRechargeRewardConfig(context.Context, *GetFirstRechargeRewardConfigRequest) (*GetFirstRechargeRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFirstRechargeRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetFirstRechargeRewardConfig not implemented")
}
func (UnimplementedAdminFirstRechargeRewardServiceServer) UpdateFirstRechargeRewardConfig(context.Context, *UpdateFirstRechargeRewardConfigRequest) (*UpdateFirstRechargeRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateFirstRechargeRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateFirstRechargeRewardConfig not implemented")
}
func (UnimplementedAdminFirstRechargeRewardServiceServer) ListFirstRechargeRewardClaims(context.Context, *ListFirstRechargeRewardClaimsRequest) (*ListFirstRechargeRewardClaimsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFirstRechargeRewardClaims not implemented")
return nil, status.Error(codes.Unimplemented, "method ListFirstRechargeRewardClaims not implemented")
}
func (UnimplementedAdminFirstRechargeRewardServiceServer) mustEmbedUnimplementedAdminFirstRechargeRewardServiceServer() {
}
@ -3000,7 +3038,7 @@ type UnsafeAdminFirstRechargeRewardServiceServer interface {
}
func RegisterAdminFirstRechargeRewardServiceServer(s grpc.ServiceRegistrar, srv AdminFirstRechargeRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminFirstRechargeRewardServiceServer was
// If the following call panics, it indicates UnimplementedAdminFirstRechargeRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -3150,10 +3188,10 @@ type CumulativeRechargeRewardServiceServer interface {
type UnimplementedCumulativeRechargeRewardServiceServer struct{}
func (UnimplementedCumulativeRechargeRewardServiceServer) GetCumulativeRechargeRewardStatus(context.Context, *GetCumulativeRechargeRewardStatusRequest) (*GetCumulativeRechargeRewardStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCumulativeRechargeRewardStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method GetCumulativeRechargeRewardStatus not implemented")
}
func (UnimplementedCumulativeRechargeRewardServiceServer) ConsumeCumulativeRechargeReward(context.Context, *ConsumeCumulativeRechargeRewardRequest) (*ConsumeCumulativeRechargeRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeCumulativeRechargeReward not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeCumulativeRechargeReward not implemented")
}
func (UnimplementedCumulativeRechargeRewardServiceServer) mustEmbedUnimplementedCumulativeRechargeRewardServiceServer() {
}
@ -3167,7 +3205,7 @@ type UnsafeCumulativeRechargeRewardServiceServer interface {
}
func RegisterCumulativeRechargeRewardServiceServer(s grpc.ServiceRegistrar, srv CumulativeRechargeRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedCumulativeRechargeRewardServiceServer was
// If the following call panics, it indicates UnimplementedCumulativeRechargeRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -3308,13 +3346,13 @@ type AdminCumulativeRechargeRewardServiceServer interface {
type UnimplementedAdminCumulativeRechargeRewardServiceServer struct{}
func (UnimplementedAdminCumulativeRechargeRewardServiceServer) GetCumulativeRechargeRewardConfig(context.Context, *GetCumulativeRechargeRewardConfigRequest) (*GetCumulativeRechargeRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCumulativeRechargeRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetCumulativeRechargeRewardConfig not implemented")
}
func (UnimplementedAdminCumulativeRechargeRewardServiceServer) UpdateCumulativeRechargeRewardConfig(context.Context, *UpdateCumulativeRechargeRewardConfigRequest) (*UpdateCumulativeRechargeRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateCumulativeRechargeRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateCumulativeRechargeRewardConfig not implemented")
}
func (UnimplementedAdminCumulativeRechargeRewardServiceServer) ListCumulativeRechargeRewardGrants(context.Context, *ListCumulativeRechargeRewardGrantsRequest) (*ListCumulativeRechargeRewardGrantsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCumulativeRechargeRewardGrants not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCumulativeRechargeRewardGrants not implemented")
}
func (UnimplementedAdminCumulativeRechargeRewardServiceServer) mustEmbedUnimplementedAdminCumulativeRechargeRewardServiceServer() {
}
@ -3328,7 +3366,7 @@ type UnsafeAdminCumulativeRechargeRewardServiceServer interface {
}
func RegisterAdminCumulativeRechargeRewardServiceServer(s grpc.ServiceRegistrar, srv AdminCumulativeRechargeRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminCumulativeRechargeRewardServiceServer was
// If the following call panics, it indicates UnimplementedAdminCumulativeRechargeRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -3504,16 +3542,16 @@ type AdminRoomTurnoverRewardServiceServer interface {
type UnimplementedAdminRoomTurnoverRewardServiceServer struct{}
func (UnimplementedAdminRoomTurnoverRewardServiceServer) GetRoomTurnoverRewardConfig(context.Context, *GetRoomTurnoverRewardConfigRequest) (*GetRoomTurnoverRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomTurnoverRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRoomTurnoverRewardConfig not implemented")
}
func (UnimplementedAdminRoomTurnoverRewardServiceServer) UpdateRoomTurnoverRewardConfig(context.Context, *UpdateRoomTurnoverRewardConfigRequest) (*UpdateRoomTurnoverRewardConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomTurnoverRewardConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRoomTurnoverRewardConfig not implemented")
}
func (UnimplementedAdminRoomTurnoverRewardServiceServer) ListRoomTurnoverRewardSettlements(context.Context, *ListRoomTurnoverRewardSettlementsRequest) (*ListRoomTurnoverRewardSettlementsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomTurnoverRewardSettlements not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomTurnoverRewardSettlements not implemented")
}
func (UnimplementedAdminRoomTurnoverRewardServiceServer) RetryRoomTurnoverRewardSettlement(context.Context, *RetryRoomTurnoverRewardSettlementRequest) (*RetryRoomTurnoverRewardSettlementResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RetryRoomTurnoverRewardSettlement not implemented")
return nil, status.Error(codes.Unimplemented, "method RetryRoomTurnoverRewardSettlement not implemented")
}
func (UnimplementedAdminRoomTurnoverRewardServiceServer) mustEmbedUnimplementedAdminRoomTurnoverRewardServiceServer() {
}
@ -3527,7 +3565,7 @@ type UnsafeAdminRoomTurnoverRewardServiceServer interface {
}
func RegisterAdminRoomTurnoverRewardServiceServer(s grpc.ServiceRegistrar, srv AdminRoomTurnoverRewardServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminRoomTurnoverRewardServiceServer was
// If the following call panics, it indicates UnimplementedAdminRoomTurnoverRewardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -3764,25 +3802,25 @@ type AdminWeeklyStarServiceServer interface {
type UnimplementedAdminWeeklyStarServiceServer struct{}
func (UnimplementedAdminWeeklyStarServiceServer) ListWeeklyStarCycles(context.Context, *ListWeeklyStarCyclesRequest) (*ListWeeklyStarCyclesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWeeklyStarCycles not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWeeklyStarCycles not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) CreateWeeklyStarCycle(context.Context, *UpsertWeeklyStarCycleRequest) (*UpsertWeeklyStarCycleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateWeeklyStarCycle not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateWeeklyStarCycle not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) GetWeeklyStarCycle(context.Context, *GetWeeklyStarCycleRequest) (*GetWeeklyStarCycleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWeeklyStarCycle not implemented")
return nil, status.Error(codes.Unimplemented, "method GetWeeklyStarCycle not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) UpdateWeeklyStarCycle(context.Context, *UpsertWeeklyStarCycleRequest) (*UpsertWeeklyStarCycleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWeeklyStarCycle not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateWeeklyStarCycle not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) SetWeeklyStarCycleStatus(context.Context, *SetWeeklyStarCycleStatusRequest) (*SetWeeklyStarCycleStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetWeeklyStarCycleStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetWeeklyStarCycleStatus not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) ListWeeklyStarLeaderboard(context.Context, *ListWeeklyStarLeaderboardRequest) (*ListWeeklyStarLeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWeeklyStarLeaderboard not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWeeklyStarLeaderboard not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) ListWeeklyStarSettlements(context.Context, *ListWeeklyStarSettlementsRequest) (*ListWeeklyStarSettlementsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWeeklyStarSettlements not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWeeklyStarSettlements not implemented")
}
func (UnimplementedAdminWeeklyStarServiceServer) mustEmbedUnimplementedAdminWeeklyStarServiceServer() {
}
@ -3796,7 +3834,7 @@ type UnsafeAdminWeeklyStarServiceServer interface {
}
func RegisterAdminWeeklyStarServiceServer(s grpc.ServiceRegistrar, srv AdminWeeklyStarServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminWeeklyStarServiceServer was
// If the following call panics, it indicates UnimplementedAdminWeeklyStarServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -4034,10 +4072,10 @@ type SevenDayCheckInServiceServer interface {
type UnimplementedSevenDayCheckInServiceServer struct{}
func (UnimplementedSevenDayCheckInServiceServer) GetSevenDayCheckInStatus(context.Context, *GetSevenDayCheckInStatusRequest) (*GetSevenDayCheckInStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSevenDayCheckInStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method GetSevenDayCheckInStatus not implemented")
}
func (UnimplementedSevenDayCheckInServiceServer) SignSevenDayCheckIn(context.Context, *SignSevenDayCheckInRequest) (*SignSevenDayCheckInResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignSevenDayCheckIn not implemented")
return nil, status.Error(codes.Unimplemented, "method SignSevenDayCheckIn not implemented")
}
func (UnimplementedSevenDayCheckInServiceServer) mustEmbedUnimplementedSevenDayCheckInServiceServer() {
}
@ -4051,7 +4089,7 @@ type UnsafeSevenDayCheckInServiceServer interface {
}
func RegisterSevenDayCheckInServiceServer(s grpc.ServiceRegistrar, srv SevenDayCheckInServiceServer) {
// If the following call pancis, it indicates UnimplementedSevenDayCheckInServiceServer was
// If the following call panics, it indicates UnimplementedSevenDayCheckInServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -4192,13 +4230,13 @@ type AdminSevenDayCheckInServiceServer interface {
type UnimplementedAdminSevenDayCheckInServiceServer struct{}
func (UnimplementedAdminSevenDayCheckInServiceServer) GetSevenDayCheckInConfig(context.Context, *GetSevenDayCheckInConfigRequest) (*GetSevenDayCheckInConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSevenDayCheckInConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetSevenDayCheckInConfig not implemented")
}
func (UnimplementedAdminSevenDayCheckInServiceServer) UpdateSevenDayCheckInConfig(context.Context, *UpdateSevenDayCheckInConfigRequest) (*UpdateSevenDayCheckInConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateSevenDayCheckInConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateSevenDayCheckInConfig not implemented")
}
func (UnimplementedAdminSevenDayCheckInServiceServer) ListSevenDayCheckInClaims(context.Context, *ListSevenDayCheckInClaimsRequest) (*ListSevenDayCheckInClaimsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSevenDayCheckInClaims not implemented")
return nil, status.Error(codes.Unimplemented, "method ListSevenDayCheckInClaims not implemented")
}
func (UnimplementedAdminSevenDayCheckInServiceServer) mustEmbedUnimplementedAdminSevenDayCheckInServiceServer() {
}
@ -4212,7 +4250,7 @@ type UnsafeAdminSevenDayCheckInServiceServer interface {
}
func RegisterAdminSevenDayCheckInServiceServer(s grpc.ServiceRegistrar, srv AdminSevenDayCheckInServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminSevenDayCheckInServiceServer was
// If the following call panics, it indicates UnimplementedAdminSevenDayCheckInServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -4388,16 +4426,16 @@ type AdminGrowthLevelServiceServer interface {
type UnimplementedAdminGrowthLevelServiceServer struct{}
func (UnimplementedAdminGrowthLevelServiceServer) ListLevelConfig(context.Context, *ListLevelConfigRequest) (*ListLevelConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLevelConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method ListLevelConfig not implemented")
}
func (UnimplementedAdminGrowthLevelServiceServer) UpsertLevelTrack(context.Context, *UpsertLevelTrackRequest) (*UpsertLevelTrackResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertLevelTrack not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertLevelTrack not implemented")
}
func (UnimplementedAdminGrowthLevelServiceServer) UpsertLevelRule(context.Context, *UpsertLevelRuleRequest) (*UpsertLevelRuleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertLevelRule not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertLevelRule not implemented")
}
func (UnimplementedAdminGrowthLevelServiceServer) UpsertLevelTier(context.Context, *UpsertLevelTierRequest) (*UpsertLevelTierResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertLevelTier not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertLevelTier not implemented")
}
func (UnimplementedAdminGrowthLevelServiceServer) mustEmbedUnimplementedAdminGrowthLevelServiceServer() {
}
@ -4411,7 +4449,7 @@ type UnsafeAdminGrowthLevelServiceServer interface {
}
func RegisterAdminGrowthLevelServiceServer(s grpc.ServiceRegistrar, srv AdminGrowthLevelServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminGrowthLevelServiceServer was
// If the following call panics, it indicates UnimplementedAdminGrowthLevelServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -4583,10 +4621,10 @@ type AdminAchievementServiceServer interface {
type UnimplementedAdminAchievementServiceServer struct{}
func (UnimplementedAdminAchievementServiceServer) ListAchievementDefinitions(context.Context, *ListAchievementsRequest) (*ListAchievementsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAchievementDefinitions not implemented")
return nil, status.Error(codes.Unimplemented, "method ListAchievementDefinitions not implemented")
}
func (UnimplementedAdminAchievementServiceServer) UpsertAchievementDefinition(context.Context, *UpsertAchievementDefinitionRequest) (*UpsertAchievementDefinitionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertAchievementDefinition not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertAchievementDefinition not implemented")
}
func (UnimplementedAdminAchievementServiceServer) mustEmbedUnimplementedAdminAchievementServiceServer() {
}
@ -4600,7 +4638,7 @@ type UnsafeAdminAchievementServiceServer interface {
}
func RegisterAdminAchievementServiceServer(s grpc.ServiceRegistrar, srv AdminAchievementServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminAchievementServiceServer was
// If the following call panics, it indicates UnimplementedAdminAchievementServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -4767,19 +4805,19 @@ type AdminLuckyGiftServiceServer interface {
type UnimplementedAdminLuckyGiftServiceServer struct{}
func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftConfig(context.Context, *GetLuckyGiftConfigRequest) (*GetLuckyGiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLuckyGiftConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetLuckyGiftConfig not implemented")
}
func (UnimplementedAdminLuckyGiftServiceServer) UpsertLuckyGiftConfig(context.Context, *UpsertLuckyGiftConfigRequest) (*UpsertLuckyGiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertLuckyGiftConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertLuckyGiftConfig not implemented")
}
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftConfigs(context.Context, *ListLuckyGiftConfigsRequest) (*ListLuckyGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLuckyGiftConfigs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftConfigs not implemented")
}
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftDraws(context.Context, *ListLuckyGiftDrawsRequest) (*ListLuckyGiftDrawsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLuckyGiftDraws not implemented")
return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftDraws not implemented")
}
func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftDrawSummary(context.Context, *GetLuckyGiftDrawSummaryRequest) (*GetLuckyGiftDrawSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLuckyGiftDrawSummary not implemented")
return nil, status.Error(codes.Unimplemented, "method GetLuckyGiftDrawSummary not implemented")
}
func (UnimplementedAdminLuckyGiftServiceServer) mustEmbedUnimplementedAdminLuckyGiftServiceServer() {}
func (UnimplementedAdminLuckyGiftServiceServer) testEmbeddedByValue() {}
@ -4792,7 +4830,7 @@ type UnsafeAdminLuckyGiftServiceServer interface {
}
func RegisterAdminLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv AdminLuckyGiftServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminLuckyGiftServiceServer was
// If the following call panics, it indicates UnimplementedAdminLuckyGiftServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -95,6 +95,7 @@ message RoomMicChanged {
int64 publish_event_time_ms = 10;
bool mic_muted = 11;
string seat_status = 12;
int64 target_gift_value = 13;
}
// RoomMicSeatLocked
@ -163,6 +164,7 @@ message RoomGiftSent {
string gift_name = 15;
string gift_icon_url = 16;
string gift_animation_url = 17;
int64 target_gift_value = 18;
}
// RoomHeatChanged

File diff suppressed because it is too large Load Diff

View File

@ -83,6 +83,32 @@ message ListGamesResponse {
int64 server_time_ms = 2;
}
message ListRecentGamesRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string scene = 3;
string room_id = 4;
int64 region_id = 5;
string language = 6;
string client_platform = 7;
int32 page_size = 8;
}
message GetBridgeScriptRequest {
RequestMeta meta = 1;
}
message GameBridgeScriptConfig {
string bridge_script_url = 1;
string bridge_script_version = 2;
string bridge_script_sha256 = 3;
}
message GetBridgeScriptResponse {
GameBridgeScriptConfig config = 1;
int64 server_time_ms = 2;
}
message LaunchGameRequest {
RequestMeta meta = 1;
int64 user_id = 2;
@ -196,6 +222,8 @@ message DeleteCatalogResponse {
service GameAppService {
rpc ListGames(ListGamesRequest) returns (ListGamesResponse);
rpc ListRecentGames(ListRecentGamesRequest) returns (ListGamesResponse);
rpc GetBridgeScript(GetBridgeScriptRequest) returns (GetBridgeScriptResponse);
rpc LaunchGame(LaunchGameRequest) returns (LaunchGameResponse);
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/game/v1/game.proto
@ -19,8 +19,10 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames"
GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame"
GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames"
GameAppService_ListRecentGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListRecentGames"
GameAppService_GetBridgeScript_FullMethodName = "/hyapp.game.v1.GameAppService/GetBridgeScript"
GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame"
)
// GameAppServiceClient is the client API for GameAppService service.
@ -28,6 +30,8 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GameAppServiceClient interface {
ListGames(ctx context.Context, in *ListGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error)
ListRecentGames(ctx context.Context, in *ListRecentGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error)
GetBridgeScript(ctx context.Context, in *GetBridgeScriptRequest, opts ...grpc.CallOption) (*GetBridgeScriptResponse, error)
LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error)
}
@ -49,6 +53,26 @@ func (c *gameAppServiceClient) ListGames(ctx context.Context, in *ListGamesReque
return out, nil
}
func (c *gameAppServiceClient) ListRecentGames(ctx context.Context, in *ListRecentGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListGamesResponse)
err := c.cc.Invoke(ctx, GameAppService_ListRecentGames_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *gameAppServiceClient) GetBridgeScript(ctx context.Context, in *GetBridgeScriptRequest, opts ...grpc.CallOption) (*GetBridgeScriptResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetBridgeScriptResponse)
err := c.cc.Invoke(ctx, GameAppService_GetBridgeScript_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *gameAppServiceClient) LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LaunchGameResponse)
@ -64,6 +88,8 @@ func (c *gameAppServiceClient) LaunchGame(ctx context.Context, in *LaunchGameReq
// for forward compatibility.
type GameAppServiceServer interface {
ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error)
ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error)
GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error)
LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error)
mustEmbedUnimplementedGameAppServiceServer()
}
@ -76,10 +102,16 @@ type GameAppServiceServer interface {
type UnimplementedGameAppServiceServer struct{}
func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGames not implemented")
return nil, status.Error(codes.Unimplemented, "method ListGames not implemented")
}
func (UnimplementedGameAppServiceServer) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListRecentGames not implemented")
}
func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetBridgeScript not implemented")
}
func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LaunchGame not implemented")
return nil, status.Error(codes.Unimplemented, "method LaunchGame not implemented")
}
func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {}
func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {}
@ -92,7 +124,7 @@ type UnsafeGameAppServiceServer interface {
}
func RegisterGameAppServiceServer(s grpc.ServiceRegistrar, srv GameAppServiceServer) {
// If the following call pancis, it indicates UnimplementedGameAppServiceServer was
// If the following call panics, it indicates UnimplementedGameAppServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -120,6 +152,42 @@ func _GameAppService_ListGames_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _GameAppService_ListRecentGames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRecentGamesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GameAppServiceServer).ListRecentGames(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: GameAppService_ListRecentGames_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GameAppServiceServer).ListRecentGames(ctx, req.(*ListRecentGamesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GameAppService_GetBridgeScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBridgeScriptRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GameAppServiceServer).GetBridgeScript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: GameAppService_GetBridgeScript_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GameAppServiceServer).GetBridgeScript(ctx, req.(*GetBridgeScriptRequest))
}
return interceptor(ctx, in, info, handler)
}
func _GameAppService_LaunchGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LaunchGameRequest)
if err := dec(in); err != nil {
@ -149,6 +217,14 @@ var GameAppService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListGames",
Handler: _GameAppService_ListGames_Handler,
},
{
MethodName: "ListRecentGames",
Handler: _GameAppService_ListRecentGames_Handler,
},
{
MethodName: "GetBridgeScript",
Handler: _GameAppService_GetBridgeScript_Handler,
},
{
MethodName: "LaunchGame",
Handler: _GameAppService_LaunchGame_Handler,
@ -203,7 +279,7 @@ type GameCallbackServiceServer interface {
type UnimplementedGameCallbackServiceServer struct{}
func (UnimplementedGameCallbackServiceServer) HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method HandleCallback not implemented")
return nil, status.Error(codes.Unimplemented, "method HandleCallback not implemented")
}
func (UnimplementedGameCallbackServiceServer) mustEmbedUnimplementedGameCallbackServiceServer() {}
func (UnimplementedGameCallbackServiceServer) testEmbeddedByValue() {}
@ -216,7 +292,7 @@ type UnsafeGameCallbackServiceServer interface {
}
func RegisterGameCallbackServiceServer(s grpc.ServiceRegistrar, srv GameCallbackServiceServer) {
// If the following call pancis, it indicates UnimplementedGameCallbackServiceServer was
// If the following call panics, it indicates UnimplementedGameCallbackServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -309,7 +385,7 @@ type GameCronServiceServer interface {
type UnimplementedGameCronServiceServer struct{}
func (UnimplementedGameCronServiceServer) ProcessLevelEventOutboxBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented")
}
func (UnimplementedGameCronServiceServer) mustEmbedUnimplementedGameCronServiceServer() {}
func (UnimplementedGameCronServiceServer) testEmbeddedByValue() {}
@ -322,7 +398,7 @@ type UnsafeGameCronServiceServer interface {
}
func RegisterGameCronServiceServer(s grpc.ServiceRegistrar, srv GameCronServiceServer) {
// If the following call pancis, it indicates UnimplementedGameCronServiceServer was
// If the following call panics, it indicates UnimplementedGameCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -476,22 +552,22 @@ type GameAdminServiceServer interface {
type UnimplementedGameAdminServiceServer struct{}
func (UnimplementedGameAdminServiceServer) ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPlatforms not implemented")
return nil, status.Error(codes.Unimplemented, "method ListPlatforms not implemented")
}
func (UnimplementedGameAdminServiceServer) UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertPlatform not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertPlatform not implemented")
}
func (UnimplementedGameAdminServiceServer) ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCatalog not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertCatalog not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGameStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetGameStatus not implemented")
}
func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteCatalog not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteCatalog not implemented")
}
func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {}
func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {}
@ -504,7 +580,7 @@ type UnsafeGameAdminServiceServer interface {
}
func RegisterGameAdminServiceServer(s grpc.ServiceRegistrar, srv GameAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedGameAdminServiceServer was
// If the following call panics, it indicates UnimplementedGameAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ message RoomUser {
string role = 2;
int64 joined_at_ms = 3;
int64 last_seen_at_ms = 4;
int64 gift_value = 5;
}
// RoomOnlineUser 线
@ -67,6 +68,8 @@ message SeatState {
string seat_status = 10;
// mic_heartbeat_at_ms mic_session UTC epoch ms
int64 mic_heartbeat_at_ms = 11;
// gift_value
int64 gift_value = 12;
}
// RankItem
@ -425,6 +428,8 @@ message CreateRoomRequest {
string room_description = 7;
// room_short_id ID default/current display_user_id
string room_short_id = 8;
// owner_country_code gateway user-service
string owner_country_code = 9;
}
// CreateRoomResponse
@ -901,6 +906,8 @@ message ListRoomsRequest {
string cursor = 5;
int32 limit = 6;
string query = 7;
// country_code gateway
string country_code = 8;
}
// ListRoomFeedsRequest Mine visited/friend/following/followed
@ -947,6 +954,7 @@ message RoomListItem {
string app_code = 13;
string room_short_id = 14;
bool locked = 15;
string country_code = 16;
}
// ListRoomsResponse cursor

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/room/v1/room.proto
@ -470,100 +470,100 @@ type RoomCommandServiceServer interface {
type UnimplementedRoomCommandServiceServer struct{}
func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomProfile not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRoomProfile not implemented")
}
func (UnimplementedRoomCommandServiceServer) SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SaveRoomBackground not implemented")
return nil, status.Error(codes.Unimplemented, "method SaveRoomBackground not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomBackground not implemented")
return nil, status.Error(codes.Unimplemented, "method SetRoomBackground not implemented")
}
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method JoinRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RoomHeartbeat not implemented")
return nil, status.Error(codes.Unimplemented, "method RoomHeartbeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaveRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method LeaveRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) CloseRoom(context.Context, *CloseRoomRequest) (*CloseRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method CloseRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoom(context.Context, *AdminUpdateRoomRequest) (*AdminUpdateRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminDeleteRoom(context.Context, *AdminDeleteRoomRequest) (*AdminDeleteRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminDeleteRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminDeleteRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomRocketConfig(context.Context, *AdminUpdateRoomRocketConfigRequest) (*AdminUpdateRoomRocketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomSeatConfig(context.Context, *AdminUpdateRoomSeatConfigRequest) (*AdminUpdateRoomSeatConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminCreateRoomPin(context.Context, *AdminCreateRoomPinRequest) (*AdminCreateRoomPinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreateRoomPin not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminCreateRoomPin not implemented")
}
func (UnimplementedRoomCommandServiceServer) AdminCancelRoomPin(context.Context, *AdminCancelRoomPinRequest) (*AdminCancelRoomPinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCancelRoomPin not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminCancelRoomPin not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicUp(context.Context, *MicUpRequest) (*MicUpResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicUp not implemented")
return nil, status.Error(codes.Unimplemented, "method MicUp not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicDown not implemented")
return nil, status.Error(codes.Unimplemented, "method MicDown not implemented")
}
func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeMicSeat not implemented")
return nil, status.Error(codes.Unimplemented, "method ChangeMicSeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmMicPublishing not implemented")
return nil, status.Error(codes.Unimplemented, "method ConfirmMicPublishing not implemented")
}
func (UnimplementedRoomCommandServiceServer) MicHeartbeat(context.Context, *MicHeartbeatRequest) (*MicHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicHeartbeat not implemented")
return nil, status.Error(codes.Unimplemented, "method MicHeartbeat not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMicMute not implemented")
return nil, status.Error(codes.Unimplemented, "method SetMicMute not implemented")
}
func (UnimplementedRoomCommandServiceServer) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyRTCEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ApplyRTCEvent not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMicSeatLock not implemented")
return nil, status.Error(codes.Unimplemented, "method SetMicSeatLock not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetChatEnabled not implemented")
return nil, status.Error(codes.Unimplemented, "method SetChatEnabled not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomPassword(context.Context, *SetRoomPasswordRequest) (*SetRoomPasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomPassword not implemented")
return nil, status.Error(codes.Unimplemented, "method SetRoomPassword not implemented")
}
func (UnimplementedRoomCommandServiceServer) SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomAdmin not implemented")
return nil, status.Error(codes.Unimplemented, "method SetRoomAdmin not implemented")
}
func (UnimplementedRoomCommandServiceServer) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuteUser not implemented")
return nil, status.Error(codes.Unimplemented, "method MuteUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method KickUser not implemented")
return nil, status.Error(codes.Unimplemented, "method KickUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnbanUser not implemented")
return nil, status.Error(codes.Unimplemented, "method UnbanUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) SystemEvictUser(context.Context, *SystemEvictUserRequest) (*SystemEvictUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SystemEvictUser not implemented")
return nil, status.Error(codes.Unimplemented, "method SystemEvictUser not implemented")
}
func (UnimplementedRoomCommandServiceServer) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendGift not implemented")
return nil, status.Error(codes.Unimplemented, "method SendGift not implemented")
}
func (UnimplementedRoomCommandServiceServer) FollowRoom(context.Context, *FollowRoomRequest) (*FollowRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FollowRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method FollowRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) UnfollowRoom(context.Context, *UnfollowRoomRequest) (*UnfollowRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnfollowRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method UnfollowRoom not implemented")
}
func (UnimplementedRoomCommandServiceServer) mustEmbedUnimplementedRoomCommandServiceServer() {}
func (UnimplementedRoomCommandServiceServer) testEmbeddedByValue() {}
@ -576,7 +576,7 @@ type UnsafeRoomCommandServiceServer interface {
}
func RegisterRoomCommandServiceServer(s grpc.ServiceRegistrar, srv RoomCommandServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomCommandServiceServer was
// If the following call panics, it indicates UnimplementedRoomCommandServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1364,10 +1364,10 @@ type RoomGuardServiceServer interface {
type UnimplementedRoomGuardServiceServer struct{}
func (UnimplementedRoomGuardServiceServer) CheckSpeakPermission(context.Context, *CheckSpeakPermissionRequest) (*CheckSpeakPermissionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckSpeakPermission not implemented")
return nil, status.Error(codes.Unimplemented, "method CheckSpeakPermission not implemented")
}
func (UnimplementedRoomGuardServiceServer) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyRoomPresence not implemented")
return nil, status.Error(codes.Unimplemented, "method VerifyRoomPresence not implemented")
}
func (UnimplementedRoomGuardServiceServer) mustEmbedUnimplementedRoomGuardServiceServer() {}
func (UnimplementedRoomGuardServiceServer) testEmbeddedByValue() {}
@ -1380,7 +1380,7 @@ type UnsafeRoomGuardServiceServer interface {
}
func RegisterRoomGuardServiceServer(s grpc.ServiceRegistrar, srv RoomGuardServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomGuardServiceServer was
// If the following call panics, it indicates UnimplementedRoomGuardServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1677,49 +1677,49 @@ type RoomQueryServiceServer interface {
type UnimplementedRoomQueryServiceServer struct{}
func (UnimplementedRoomQueryServiceServer) AdminListRooms(context.Context, *AdminListRoomsRequest) (*AdminListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminListRooms not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminListRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoom(context.Context, *AdminGetRoomRequest) (*AdminGetRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminGetRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoomRocketConfig(context.Context, *AdminGetRoomRocketConfigRequest) (*AdminGetRoomRocketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminGetRoomSeatConfig(context.Context, *AdminGetRoomSeatConfigRequest) (*AdminGetRoomSeatConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented")
}
func (UnimplementedRoomQueryServiceServer) AdminListRoomPins(context.Context, *AdminListRoomPinsRequest) (*AdminListRoomPinsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminListRoomPins not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminListRoomPins not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRooms not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRooms not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomFeeds not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomFeeds not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomGiftLeaderboard(context.Context, *ListRoomGiftLeaderboardRequest) (*ListRoomGiftLeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method GetMyRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentRoom not implemented")
return nil, status.Error(codes.Unimplemented, "method GetCurrentRoom not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomSnapshot not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRoomSnapshot not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
}
func (UnimplementedRoomQueryServiceServer) GetRoomRocket(context.Context, *GetRoomRocketRequest) (*GetRoomRocketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRocket not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRoomRocket not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
}
func (UnimplementedRoomQueryServiceServer) ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
}
func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {}
func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {}
@ -1732,7 +1732,7 @@ type UnsafeRoomQueryServiceServer interface {
}
func RegisterRoomQueryServiceServer(s grpc.ServiceRegistrar, srv RoomQueryServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomQueryServiceServer was
// If the following call panics, it indicates UnimplementedRoomQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc-gen-go v1.36.11
// protoc v7.35.0
// source: proto/user/v1/auth.proto
@ -11,6 +11,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -22,13 +23,12 @@ const (
// LoginPasswordRequest 使用当前 display_user_id 和已设置密码登录。
type LoginPasswordRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *LoginPasswordRequest) Reset() {
@ -84,30 +84,29 @@ func (x *LoginPasswordRequest) GetPassword() string {
// LoginThirdPartyRequest 使用三方凭证登录或注册。
type LoginThirdPartyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"`
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
Avatar string `protobuf:"bytes,10,opt,name=avatar,proto3" json:"avatar,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
Source string `protobuf:"bytes,13,opt,name=source,proto3" json:"source,omitempty"`
Platform string `protobuf:"bytes,14,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,15,opt,name=language,proto3" json:"language,omitempty"`
OsVersion string `protobuf:"bytes,16,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
BuildNumber string `protobuf:"bytes,17,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Timezone string `protobuf:"bytes,18,opt,name=timezone,proto3" json:"timezone,omitempty"`
InstallChannel string `protobuf:"bytes,19,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,20,opt,name=campaign,proto3" json:"campaign,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"`
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
Avatar string `protobuf:"bytes,10,opt,name=avatar,proto3" json:"avatar,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
Source string `protobuf:"bytes,13,opt,name=source,proto3" json:"source,omitempty"`
Platform string `protobuf:"bytes,14,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,15,opt,name=language,proto3" json:"language,omitempty"`
OsVersion string `protobuf:"bytes,16,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
BuildNumber string `protobuf:"bytes,17,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Timezone string `protobuf:"bytes,18,opt,name=timezone,proto3" json:"timezone,omitempty"`
InstallChannel string `protobuf:"bytes,19,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,20,opt,name=campaign,proto3" json:"campaign,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LoginThirdPartyRequest) Reset() {
@ -282,14 +281,13 @@ func (x *LoginThirdPartyRequest) GetCampaign() string {
// AuthResponse 返回标准认证令牌。
type AuthResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
ProfileCompleted bool `protobuf:"varint,3,opt,name=profile_completed,json=profileCompleted,proto3" json:"profile_completed,omitempty"`
OnboardingStatus string `protobuf:"bytes,4,opt,name=onboarding_status,json=onboardingStatus,proto3" json:"onboarding_status,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
ProfileCompleted bool `protobuf:"varint,3,opt,name=profile_completed,json=profileCompleted,proto3" json:"profile_completed,omitempty"`
OnboardingStatus string `protobuf:"bytes,4,opt,name=onboarding_status,json=onboardingStatus,proto3" json:"onboarding_status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AuthResponse) Reset() {
@ -352,13 +350,12 @@ func (x *AuthResponse) GetOnboardingStatus() string {
// SetPasswordRequest 为已经三方登录的用户首次设置密码。
type SetPasswordRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
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"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
unknownFields protoimpl.UnknownFields
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"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *SetPasswordRequest) Reset() {
@ -414,11 +411,10 @@ func (x *SetPasswordRequest) GetPassword() string {
// SetPasswordResponse 返回密码是否已经成功设置。
type SetPasswordResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
unknownFields protoimpl.UnknownFields
PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *SetPasswordResponse) Reset() {
@ -460,29 +456,28 @@ func (x *SetPasswordResponse) GetPasswordSet() bool {
// QuickCreateAccountRequest 为测试和三方联调快速创建一个可密码登录的完整账号。
type QuickCreateAccountRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
OsVersion string `protobuf:"bytes,10,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
BuildNumber string `protobuf:"bytes,13,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Source string `protobuf:"bytes,14,opt,name=source,proto3" json:"source,omitempty"`
InstallChannel string `protobuf:"bytes,15,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,16,opt,name=campaign,proto3" json:"campaign,omitempty"`
Platform string `protobuf:"bytes,17,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,18,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,19,opt,name=timezone,proto3" json:"timezone,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"`
Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"`
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"`
OsVersion string `protobuf:"bytes,10,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"`
Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"`
AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
BuildNumber string `protobuf:"bytes,13,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"`
Source string `protobuf:"bytes,14,opt,name=source,proto3" json:"source,omitempty"`
InstallChannel string `protobuf:"bytes,15,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"`
Campaign string `protobuf:"bytes,16,opt,name=campaign,proto3" json:"campaign,omitempty"`
Platform string `protobuf:"bytes,17,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,18,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,19,opt,name=timezone,proto3" json:"timezone,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *QuickCreateAccountRequest) Reset() {
@ -650,12 +645,11 @@ func (x *QuickCreateAccountRequest) GetTimezone() string {
// QuickCreateAccountResponse 返回新账号登录态display_user_id 即后续账号密码登录用账号。
type QuickCreateAccountResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
PasswordSet bool `protobuf:"varint,2,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
PasswordSet bool `protobuf:"varint,2,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *QuickCreateAccountResponse) Reset() {
@ -704,12 +698,11 @@ func (x *QuickCreateAccountResponse) GetPasswordSet() bool {
// RefreshTokenRequest 使用 refresh token 换取新 token。
type RefreshTokenRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RefreshTokenRequest) Reset() {
@ -758,11 +751,10 @@ func (x *RefreshTokenRequest) GetRefreshToken() string {
// RefreshTokenResponse 返回轮换后的令牌。
type RefreshTokenResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RefreshTokenResponse) Reset() {
@ -804,13 +796,12 @@ func (x *RefreshTokenResponse) GetToken() *AuthToken {
// LogoutRequest 失效指定会话或 refresh token。
type LogoutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *LogoutRequest) Reset() {
@ -866,11 +857,10 @@ func (x *LogoutRequest) GetRefreshToken() string {
// LogoutResponse 返回会话是否被失效。
type LogoutResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"`
unknownFields protoimpl.UnknownFields
Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *LogoutResponse) Reset() {
@ -912,16 +902,15 @@ func (x *LogoutResponse) GetRevoked() bool {
// RecordLoginBlockedRequest 记录 gateway 入口快拦拒绝的登录审计。
type RecordLoginBlockedRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"`
Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,4,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,5,opt,name=timezone,proto3" json:"timezone,omitempty"`
BlockReason string `protobuf:"bytes,6,opt,name=block_reason,json=blockReason,proto3" json:"block_reason,omitempty"`
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"`
Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
Language string `protobuf:"bytes,4,opt,name=language,proto3" json:"language,omitempty"`
Timezone string `protobuf:"bytes,5,opt,name=timezone,proto3" json:"timezone,omitempty"`
BlockReason string `protobuf:"bytes,6,opt,name=block_reason,json=blockReason,proto3" json:"block_reason,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RecordLoginBlockedRequest) Reset() {
@ -998,11 +987,10 @@ func (x *RecordLoginBlockedRequest) GetBlockReason() string {
// RecordLoginBlockedResponse 返回审计写入是否成功。
type RecordLoginBlockedResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Recorded bool `protobuf:"varint,1,opt,name=recorded,proto3" json:"recorded,omitempty"`
unknownFields protoimpl.UnknownFields
Recorded bool `protobuf:"varint,1,opt,name=recorded,proto3" json:"recorded,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RecordLoginBlockedResponse) Reset() {
@ -1044,13 +1032,12 @@ func (x *RecordLoginBlockedResponse) GetRecorded() bool {
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
type AppHeartbeatRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
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"`
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
unknownFields protoimpl.UnknownFields
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"`
SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *AppHeartbeatRequest) Reset() {
@ -1106,13 +1093,12 @@ func (x *AppHeartbeatRequest) GetSessionId() string {
// AppHeartbeatResponse 返回本次会话心跳写入时间。
type AppHeartbeatResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
HeartbeatAtMs int64 `protobuf:"varint,2,opt,name=heartbeat_at_ms,json=heartbeatAtMs,proto3" json:"heartbeat_at_ms,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
HeartbeatAtMs int64 `protobuf:"varint,2,opt,name=heartbeat_at_ms,json=heartbeatAtMs,proto3" json:"heartbeat_at_ms,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *AppHeartbeatResponse) Reset() {
@ -1168,241 +1154,126 @@ func (x *AppHeartbeatResponse) GetServerTimeMs() int64 {
var File_proto_user_v1_auth_proto protoreflect.FileDescriptor
var file_proto_user_v1_auth_proto_rawDesc = []byte{
0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f,
0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x8a, 0x01, 0x0a, 0x14, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73,
0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x22, 0xea, 0x04, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50,
0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d,
0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f,
0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69,
0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61,
0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61,
0x74, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0e,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a,
0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74,
0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x13, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x18, 0x14, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x22, 0xb8, 0x01,
0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e,
0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75,
0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e,
0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2b,
0x0a, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x6f,
0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69,
0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e,
0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17,
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x22, 0x38, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x53, 0x65, 0x74, 0x22, 0xcd, 0x04,
0x0a, 0x19, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d,
0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x67,
0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, 0x0a,
0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b,
0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64,
0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76,
0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,
0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69,
0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x0a,
0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61,
0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61,
0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x13, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x6f, 0x0a,
0x1a, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54,
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x53, 0x65, 0x74, 0x22, 0x6a,
0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68,
0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x46, 0x0a, 0x14, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72,
0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x2a, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x6f,
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65,
0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x76,
0x6f, 0x6b, 0x65, 0x64, 0x22, 0xdc, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67,
0x75, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67,
0x75, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65,
0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e,
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x61,
0x73, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67,
0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x22, 0x7d, 0x0a,
0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04,
0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x80, 0x01, 0x0a,
0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65,
0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61,
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72,
0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x32,
0xdc, 0x05, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64,
0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64,
0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f,
0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f,
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65,
0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61,
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26,
0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b,
0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
const file_proto_user_v1_auth_proto_rawDesc = "" +
"\n" +
"\x18proto/user/v1/auth.proto\x12\rhyapp.user.v1\x1a\x18proto/user/v1/user.proto\"\x8a\x01\n" +
"\x14LoginPasswordRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12&\n" +
"\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x1a\n" +
"\bpassword\x18\x03 \x01(\tR\bpassword\"\xea\x04\n" +
"\x16LoginThirdPartyRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1a\n" +
"\bprovider\x18\x02 \x01(\tR\bprovider\x12\x1e\n" +
"\n" +
"credential\x18\x03 \x01(\tR\n" +
"credential\x12\x1a\n" +
"\busername\x18\x04 \x01(\tR\busername\x12\x16\n" +
"\x06gender\x18\x05 \x01(\tR\x06gender\x12\x18\n" +
"\acountry\x18\x06 \x01(\tR\acountry\x12\x1f\n" +
"\vinvite_code\x18\a \x01(\tR\n" +
"inviteCode\x12\x1b\n" +
"\tdevice_id\x18\b \x01(\tR\bdeviceId\x12\x16\n" +
"\x06device\x18\t \x01(\tR\x06device\x12\x16\n" +
"\x06avatar\x18\n" +
" \x01(\tR\x06avatar\x12\x14\n" +
"\x05birth\x18\v \x01(\tR\x05birth\x12\x1f\n" +
"\vapp_version\x18\f \x01(\tR\n" +
"appVersion\x12\x16\n" +
"\x06source\x18\r \x01(\tR\x06source\x12\x1a\n" +
"\bplatform\x18\x0e \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x0f \x01(\tR\blanguage\x12\x1d\n" +
"\n" +
"os_version\x18\x10 \x01(\tR\tosVersion\x12!\n" +
"\fbuild_number\x18\x11 \x01(\tR\vbuildNumber\x12\x1a\n" +
"\btimezone\x18\x12 \x01(\tR\btimezone\x12'\n" +
"\x0finstall_channel\x18\x13 \x01(\tR\x0einstallChannel\x12\x1a\n" +
"\bcampaign\x18\x14 \x01(\tR\bcampaign\"\xb8\x01\n" +
"\fAuthResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\x12\x1e\n" +
"\vis_new_user\x18\x02 \x01(\bR\tisNewUser\x12+\n" +
"\x11profile_completed\x18\x03 \x01(\bR\x10profileCompleted\x12+\n" +
"\x11onboarding_status\x18\x04 \x01(\tR\x10onboardingStatus\"y\n" +
"\x12SetPasswordRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1a\n" +
"\bpassword\x18\x03 \x01(\tR\bpassword\"8\n" +
"\x13SetPasswordResponse\x12!\n" +
"\fpassword_set\x18\x01 \x01(\bR\vpasswordSet\"\xcd\x04\n" +
"\x19QuickCreateAccountRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1a\n" +
"\busername\x18\x03 \x01(\tR\busername\x12\x16\n" +
"\x06avatar\x18\x04 \x01(\tR\x06avatar\x12\x16\n" +
"\x06gender\x18\x05 \x01(\tR\x06gender\x12\x18\n" +
"\acountry\x18\x06 \x01(\tR\acountry\x12\x1f\n" +
"\vinvite_code\x18\a \x01(\tR\n" +
"inviteCode\x12\x1b\n" +
"\tdevice_id\x18\b \x01(\tR\bdeviceId\x12\x16\n" +
"\x06device\x18\t \x01(\tR\x06device\x12\x1d\n" +
"\n" +
"os_version\x18\n" +
" \x01(\tR\tosVersion\x12\x14\n" +
"\x05birth\x18\v \x01(\tR\x05birth\x12\x1f\n" +
"\vapp_version\x18\f \x01(\tR\n" +
"appVersion\x12!\n" +
"\fbuild_number\x18\r \x01(\tR\vbuildNumber\x12\x16\n" +
"\x06source\x18\x0e \x01(\tR\x06source\x12'\n" +
"\x0finstall_channel\x18\x0f \x01(\tR\x0einstallChannel\x12\x1a\n" +
"\bcampaign\x18\x10 \x01(\tR\bcampaign\x12\x1a\n" +
"\bplatform\x18\x11 \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x12 \x01(\tR\blanguage\x12\x1a\n" +
"\btimezone\x18\x13 \x01(\tR\btimezone\"o\n" +
"\x1aQuickCreateAccountResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\x12!\n" +
"\fpassword_set\x18\x02 \x01(\bR\vpasswordSet\"j\n" +
"\x13RefreshTokenRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12#\n" +
"\rrefresh_token\x18\x02 \x01(\tR\frefreshToken\"F\n" +
"\x14RefreshTokenResponse\x12.\n" +
"\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\"\x83\x01\n" +
"\rLogoutRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" +
"\n" +
"session_id\x18\x02 \x01(\tR\tsessionId\x12#\n" +
"\rrefresh_token\x18\x03 \x01(\tR\frefreshToken\"*\n" +
"\x0eLogoutResponse\x12\x18\n" +
"\arevoked\x18\x01 \x01(\bR\arevoked\"\xdc\x01\n" +
"\x19RecordLoginBlockedRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x18\n" +
"\achannel\x18\x02 \x01(\tR\achannel\x12\x1a\n" +
"\bplatform\x18\x03 \x01(\tR\bplatform\x12\x1a\n" +
"\blanguage\x18\x04 \x01(\tR\blanguage\x12\x1a\n" +
"\btimezone\x18\x05 \x01(\tR\btimezone\x12!\n" +
"\fblock_reason\x18\x06 \x01(\tR\vblockReason\"8\n" +
"\x1aRecordLoginBlockedResponse\x12\x1a\n" +
"\brecorded\x18\x01 \x01(\bR\brecorded\"}\n" +
"\x13AppHeartbeatRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1d\n" +
"\n" +
"session_id\x18\x03 \x01(\tR\tsessionId\"\x80\x01\n" +
"\x14AppHeartbeatResponse\x12\x1a\n" +
"\baccepted\x18\x01 \x01(\bR\baccepted\x12&\n" +
"\x0fheartbeat_at_ms\x18\x02 \x01(\x03R\rheartbeatAtMs\x12$\n" +
"\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs2\xdc\x05\n" +
"\vAuthService\x12Q\n" +
"\rLoginPassword\x12#.hyapp.user.v1.LoginPasswordRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12U\n" +
"\x0fLoginThirdParty\x12%.hyapp.user.v1.LoginThirdPartyRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12T\n" +
"\vSetPassword\x12!.hyapp.user.v1.SetPasswordRequest\x1a\".hyapp.user.v1.SetPasswordResponse\x12i\n" +
"\x12QuickCreateAccount\x12(.hyapp.user.v1.QuickCreateAccountRequest\x1a).hyapp.user.v1.QuickCreateAccountResponse\x12W\n" +
"\fRefreshToken\x12\".hyapp.user.v1.RefreshTokenRequest\x1a#.hyapp.user.v1.RefreshTokenResponse\x12E\n" +
"\x06Logout\x12\x1c.hyapp.user.v1.LogoutRequest\x1a\x1d.hyapp.user.v1.LogoutResponse\x12i\n" +
"\x12RecordLoginBlocked\x12(.hyapp.user.v1.RecordLoginBlockedRequest\x1a).hyapp.user.v1.RecordLoginBlockedResponse\x12W\n" +
"\fAppHeartbeat\x12\".hyapp.user.v1.AppHeartbeatRequest\x1a#.hyapp.user.v1.AppHeartbeatResponseB&Z$hyapp.local/api/proto/user/v1;userv1b\x06proto3"
var (
file_proto_user_v1_auth_proto_rawDescOnce sync.Once
file_proto_user_v1_auth_proto_rawDescData = file_proto_user_v1_auth_proto_rawDesc
file_proto_user_v1_auth_proto_rawDescData []byte
)
func file_proto_user_v1_auth_proto_rawDescGZIP() []byte {
file_proto_user_v1_auth_proto_rawDescOnce.Do(func() {
file_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_user_v1_auth_proto_rawDescData)
file_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_user_v1_auth_proto_rawDesc), len(file_proto_user_v1_auth_proto_rawDesc)))
})
return file_proto_user_v1_auth_proto_rawDescData
}
@ -1472,7 +1343,7 @@ func file_proto_user_v1_auth_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_user_v1_auth_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_user_v1_auth_proto_rawDesc), len(file_proto_user_v1_auth_proto_rawDesc)),
NumEnums: 0,
NumMessages: 15,
NumExtensions: 0,
@ -1483,7 +1354,6 @@ func file_proto_user_v1_auth_proto_init() {
MessageInfos: file_proto_user_v1_auth_proto_msgTypes,
}.Build()
File_proto_user_v1_auth_proto = out.File
file_proto_user_v1_auth_proto_rawDesc = nil
file_proto_user_v1_auth_proto_goTypes = nil
file_proto_user_v1_auth_proto_depIdxs = nil
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/user/v1/auth.proto
@ -158,28 +158,28 @@ type AuthServiceServer interface {
type UnimplementedAuthServiceServer struct{}
func (UnimplementedAuthServiceServer) LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginPassword not implemented")
return nil, status.Error(codes.Unimplemented, "method LoginPassword not implemented")
}
func (UnimplementedAuthServiceServer) LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginThirdParty not implemented")
return nil, status.Error(codes.Unimplemented, "method LoginThirdParty not implemented")
}
func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetPassword not implemented")
return nil, status.Error(codes.Unimplemented, "method SetPassword not implemented")
}
func (UnimplementedAuthServiceServer) QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QuickCreateAccount not implemented")
return nil, status.Error(codes.Unimplemented, "method QuickCreateAccount not implemented")
}
func (UnimplementedAuthServiceServer) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshToken not implemented")
return nil, status.Error(codes.Unimplemented, "method RefreshToken not implemented")
}
func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
return nil, status.Error(codes.Unimplemented, "method Logout not implemented")
}
func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordLoginBlocked not implemented")
return nil, status.Error(codes.Unimplemented, "method RecordLoginBlocked not implemented")
}
func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented")
return nil, status.Error(codes.Unimplemented, "method AppHeartbeat not implemented")
}
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
func (UnimplementedAuthServiceServer) testEmbeddedByValue() {}
@ -192,7 +192,7 @@ type UnsafeAuthServiceServer interface {
}
func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
// If the following call pancis, it indicates UnimplementedAuthServiceServer was
// If the following call panics, it indicates UnimplementedAuthServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/user/v1/host.proto
@ -301,61 +301,61 @@ type UserHostServiceServer interface {
type UnimplementedUserHostServiceServer struct{}
func (UnimplementedUserHostServiceServer) SearchAgencies(context.Context, *SearchAgenciesRequest) (*SearchAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SearchAgencies not implemented")
return nil, status.Error(codes.Unimplemented, "method SearchAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ApplyToAgency(context.Context, *ApplyToAgencyRequest) (*ApplyToAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyToAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method ApplyToAgency not implemented")
}
func (UnimplementedUserHostServiceServer) ReviewAgencyApplication(context.Context, *ReviewAgencyApplicationRequest) (*ReviewAgencyApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReviewAgencyApplication not implemented")
return nil, status.Error(codes.Unimplemented, "method ReviewAgencyApplication not implemented")
}
func (UnimplementedUserHostServiceServer) KickAgencyHost(context.Context, *KickAgencyHostRequest) (*KickAgencyHostResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method KickAgencyHost not implemented")
return nil, status.Error(codes.Unimplemented, "method KickAgencyHost not implemented")
}
func (UnimplementedUserHostServiceServer) InviteAgency(context.Context, *InviteAgencyRequest) (*InviteAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InviteAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method InviteAgency not implemented")
}
func (UnimplementedUserHostServiceServer) InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InviteBD not implemented")
return nil, status.Error(codes.Unimplemented, "method InviteBD not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDAgencies not implemented")
return nil, status.Error(codes.Unimplemented, "method ListBDAgencies not implemented")
}
func (UnimplementedUserHostServiceServer) ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
}
func (UnimplementedUserHostServiceServer) GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostProfile not implemented")
return nil, status.Error(codes.Unimplemented, "method GetHostProfile not implemented")
}
func (UnimplementedUserHostServiceServer) GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBDProfile not implemented")
return nil, status.Error(codes.Unimplemented, "method GetBDProfile not implemented")
}
func (UnimplementedUserHostServiceServer) GetCoinSellerProfile(context.Context, *GetCoinSellerProfileRequest) (*GetCoinSellerProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCoinSellerProfile not implemented")
return nil, status.Error(codes.Unimplemented, "method GetCoinSellerProfile not implemented")
}
func (UnimplementedUserHostServiceServer) ListActiveCoinSellersInMyRegion(context.Context, *ListActiveCoinSellersInMyRegionRequest) (*ListActiveCoinSellersInMyRegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented")
return nil, status.Error(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented")
}
func (UnimplementedUserHostServiceServer) GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserRoleSummary not implemented")
return nil, status.Error(codes.Unimplemented, "method GetUserRoleSummary not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgency(context.Context, *GetAgencyRequest) (*GetAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method GetAgency not implemented")
}
func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckBusinessCapability not implemented")
return nil, status.Error(codes.Unimplemented, "method CheckBusinessCapability not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyMembers not implemented")
return nil, status.Error(codes.Unimplemented, "method GetAgencyMembers not implemented")
}
func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyApplications not implemented")
return nil, status.Error(codes.Unimplemented, "method GetAgencyApplications not implemented")
}
func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {}
func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {}
@ -368,7 +368,7 @@ type UnsafeUserHostServiceServer interface {
}
func RegisterUserHostServiceServer(s grpc.ServiceRegistrar, srv UserHostServiceServer) {
// If the following call pancis, it indicates UnimplementedUserHostServiceServer was
// If the following call panics, it indicates UnimplementedUserHostServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -961,31 +961,31 @@ type UserHostAdminServiceServer interface {
type UnimplementedUserHostAdminServiceServer struct{}
func (UnimplementedUserHostAdminServiceServer) CreateBDLeader(context.Context, *CreateBDLeaderRequest) (*CreateBDLeaderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateBDLeader not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateBDLeader not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateBD(context.Context, *CreateBDRequest) (*CreateBDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateBD not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateBD not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetBDStatus(context.Context, *SetBDStatusRequest) (*SetBDStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetBDStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetBDStatus not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateCoinSeller(context.Context, *CreateCoinSellerRequest) (*CreateCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateCoinSeller not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateCoinSeller not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetCoinSellerStatus(context.Context, *SetCoinSellerStatusRequest) (*SetCoinSellerStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetCoinSellerStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetCoinSellerStatus not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CreateAgency(context.Context, *CreateAgencyRequest) (*CreateAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) CloseAgency(context.Context, *CloseAgencyRequest) (*CloseAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method CloseAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) DeleteAgency(context.Context, *DeleteAgencyRequest) (*DeleteAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteAgency not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteAgency not implemented")
}
func (UnimplementedUserHostAdminServiceServer) SetAgencyJoinEnabled(context.Context, *SetAgencyJoinEnabledRequest) (*SetAgencyJoinEnabledResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented")
return nil, status.Error(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented")
}
func (UnimplementedUserHostAdminServiceServer) mustEmbedUnimplementedUserHostAdminServiceServer() {}
func (UnimplementedUserHostAdminServiceServer) testEmbeddedByValue() {}
@ -998,7 +998,7 @@ type UnsafeUserHostAdminServiceServer interface {
}
func RegisterUserHostAdminServiceServer(s grpc.ServiceRegistrar, srv UserHostAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedUserHostAdminServiceServer was
// If the following call panics, it indicates UnimplementedUserHostAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -408,6 +408,14 @@ message CPRelationship {
reserved "equipped";
int64 formed_at_ms = 9;
int64 updated_at_ms = 10;
int64 current_level_threshold = 11;
int64 next_level_threshold = 12;
int64 needed_for_next_level = 13;
int32 level_progress_percent = 14;
bool max_level = 15;
// breakup_cost_coins active ended
int64 breakup_cost_coins = 16;
int64 ended_at_ms = 17;
}
message ListCPApplicationsRequest {
@ -462,6 +470,52 @@ message ListCPRelationshipsResponse {
reserved "max_count_by_relation_type";
}
message PrepareBreakCPRelationshipRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string relationship_id = 3;
string command_id = 4;
}
message PrepareBreakCPRelationshipResponse {
CPRelationship relationship = 1;
string command_id = 2;
string status = 3;
int64 breakup_cost_coins = 4;
string wallet_transaction_id = 5;
int64 coin_balance_after = 6;
}
message ConfirmBreakCPRelationshipRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string relationship_id = 3;
string command_id = 4;
string wallet_transaction_id = 5;
int64 paid_coin_amount = 6;
int64 coin_balance_after = 7;
}
message ConfirmBreakCPRelationshipResponse {
CPRelationship relationship = 1;
int64 breakup_cost_coins = 2;
string wallet_transaction_id = 3;
int64 coin_balance_after = 4;
}
message CancelBreakCPRelationshipRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string relationship_id = 3;
string command_id = 4;
string reason = 5;
}
message CancelBreakCPRelationshipResponse {
string command_id = 1;
string status = 2;
}
message RoomGiftCPEvent {
RequestMeta meta = 1;
string event_id = 2;
@ -619,6 +673,58 @@ message SetUserStatusResponse {
string access_token_revoke_error = 11;
}
message ManagerUserBlock {
string block_id = 1;
int64 manager_user_id = 2;
int64 target_user_id = 3;
string target_display_user_id = 4;
string target_username = 5;
string target_avatar = 6;
string country = 7;
int64 blocked_until_ms = 8;
string status = 9;
string reason = 10;
int64 created_at_ms = 11;
int64 updated_at_ms = 12;
}
message CreateManagerUserBlockRequest {
RequestMeta meta = 1;
string command_id = 2;
int64 manager_user_id = 3;
int64 target_user_id = 4;
int64 blocked_until_ms = 5;
string reason = 6;
}
message CreateManagerUserBlockResponse {
ManagerUserBlock block = 1;
SetUserStatusResponse status = 2;
}
message ListManagerUserBlocksRequest {
RequestMeta meta = 1;
int64 manager_user_id = 2;
string status = 3;
int32 page_size = 4;
}
message ListManagerUserBlocksResponse {
repeated ManagerUserBlock blocks = 1;
}
message UnblockManagerUserRequest {
RequestMeta meta = 1;
int64 manager_user_id = 2;
string block_id = 3;
string reason = 4;
}
message UnblockManagerUserResponse {
ManagerUserBlock block = 1;
SetUserStatusResponse status = 2;
}
// CompleteOnboardingRequest
message CompleteOnboardingRequest {
RequestMeta meta = 1;
@ -874,6 +980,9 @@ service UserService {
rpc ChangeUserCountry(ChangeUserCountryRequest) returns (ChangeUserCountryResponse);
rpc AdminChangeUserCountry(AdminChangeUserCountryRequest) returns (ChangeUserCountryResponse);
rpc SetUserStatus(SetUserStatusRequest) returns (SetUserStatusResponse);
rpc CreateManagerUserBlock(CreateManagerUserBlockRequest) returns (CreateManagerUserBlockResponse);
rpc ListManagerUserBlocks(ListManagerUserBlocksRequest) returns (ListManagerUserBlocksResponse);
rpc UnblockManagerUser(UnblockManagerUserRequest) returns (UnblockManagerUserResponse);
rpc CompleteOnboarding(CompleteOnboardingRequest) returns (CompleteOnboardingResponse);
}
@ -898,6 +1007,9 @@ service UserCPService {
rpc AcceptCPApplication(AcceptCPApplicationRequest) returns (AcceptCPApplicationResponse);
rpc RejectCPApplication(RejectCPApplicationRequest) returns (RejectCPApplicationResponse);
rpc ListCPRelationships(ListCPRelationshipsRequest) returns (ListCPRelationshipsResponse);
rpc PrepareBreakCPRelationship(PrepareBreakCPRelationshipRequest) returns (PrepareBreakCPRelationshipResponse);
rpc ConfirmBreakCPRelationship(ConfirmBreakCPRelationshipRequest) returns (ConfirmBreakCPRelationshipResponse);
rpc CancelBreakCPRelationship(CancelBreakCPRelationshipRequest) returns (CancelBreakCPRelationshipResponse);
}
// UserCPInternalService owner outbox worker App
@ -910,6 +1022,7 @@ service UserCronService {
rpc ProcessLoginIPRiskBatch(CronBatchRequest) returns (CronBatchResponse);
rpc ProcessRegionRebuildBatch(CronBatchRequest) returns (CronBatchResponse);
rpc CompensateMicOpenSessions(CronBatchRequest) returns (CronBatchResponse);
rpc ExpireManagerUserBlocks(CronBatchRequest) returns (CronBatchResponse);
}
// UserDeviceService App token

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/user/v1/user.proto
@ -29,6 +29,9 @@ const (
UserService_ChangeUserCountry_FullMethodName = "/hyapp.user.v1.UserService/ChangeUserCountry"
UserService_AdminChangeUserCountry_FullMethodName = "/hyapp.user.v1.UserService/AdminChangeUserCountry"
UserService_SetUserStatus_FullMethodName = "/hyapp.user.v1.UserService/SetUserStatus"
UserService_CreateManagerUserBlock_FullMethodName = "/hyapp.user.v1.UserService/CreateManagerUserBlock"
UserService_ListManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserService/ListManagerUserBlocks"
UserService_UnblockManagerUser_FullMethodName = "/hyapp.user.v1.UserService/UnblockManagerUser"
UserService_CompleteOnboarding_FullMethodName = "/hyapp.user.v1.UserService/CompleteOnboarding"
)
@ -48,6 +51,9 @@ type UserServiceClient interface {
ChangeUserCountry(ctx context.Context, in *ChangeUserCountryRequest, opts ...grpc.CallOption) (*ChangeUserCountryResponse, error)
AdminChangeUserCountry(ctx context.Context, in *AdminChangeUserCountryRequest, opts ...grpc.CallOption) (*ChangeUserCountryResponse, error)
SetUserStatus(ctx context.Context, in *SetUserStatusRequest, opts ...grpc.CallOption) (*SetUserStatusResponse, error)
CreateManagerUserBlock(ctx context.Context, in *CreateManagerUserBlockRequest, opts ...grpc.CallOption) (*CreateManagerUserBlockResponse, error)
ListManagerUserBlocks(ctx context.Context, in *ListManagerUserBlocksRequest, opts ...grpc.CallOption) (*ListManagerUserBlocksResponse, error)
UnblockManagerUser(ctx context.Context, in *UnblockManagerUserRequest, opts ...grpc.CallOption) (*UnblockManagerUserResponse, error)
CompleteOnboarding(ctx context.Context, in *CompleteOnboardingRequest, opts ...grpc.CallOption) (*CompleteOnboardingResponse, error)
}
@ -159,6 +165,36 @@ func (c *userServiceClient) SetUserStatus(ctx context.Context, in *SetUserStatus
return out, nil
}
func (c *userServiceClient) CreateManagerUserBlock(ctx context.Context, in *CreateManagerUserBlockRequest, opts ...grpc.CallOption) (*CreateManagerUserBlockResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreateManagerUserBlockResponse)
err := c.cc.Invoke(ctx, UserService_CreateManagerUserBlock_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) ListManagerUserBlocks(ctx context.Context, in *ListManagerUserBlocksRequest, opts ...grpc.CallOption) (*ListManagerUserBlocksResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListManagerUserBlocksResponse)
err := c.cc.Invoke(ctx, UserService_ListManagerUserBlocks_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) UnblockManagerUser(ctx context.Context, in *UnblockManagerUserRequest, opts ...grpc.CallOption) (*UnblockManagerUserResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UnblockManagerUserResponse)
err := c.cc.Invoke(ctx, UserService_UnblockManagerUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) CompleteOnboarding(ctx context.Context, in *CompleteOnboardingRequest, opts ...grpc.CallOption) (*CompleteOnboardingResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CompleteOnboardingResponse)
@ -185,6 +221,9 @@ type UserServiceServer interface {
ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error)
AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error)
SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error)
CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error)
ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error)
UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error)
CompleteOnboarding(context.Context, *CompleteOnboardingRequest) (*CompleteOnboardingResponse, error)
mustEmbedUnimplementedUserServiceServer()
}
@ -197,37 +236,46 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct{}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
return nil, status.Error(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BusinessUserLookup not implemented")
return nil, status.Error(codes.Unimplemented, "method BusinessUserLookup not implemented")
}
func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyProfileStats not implemented")
return nil, status.Error(codes.Unimplemented, "method GetMyProfileStats not implemented")
}
func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUsers not implemented")
return nil, status.Error(codes.Unimplemented, "method BatchGetUsers not implemented")
}
func (UnimplementedUserServiceServer) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserIDs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListUserIDs not implemented")
}
func (UnimplementedUserServiceServer) GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented")
return nil, status.Error(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented")
}
func (UnimplementedUserServiceServer) UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UpdateUserProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfile not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateUserProfile not implemented")
}
func (UnimplementedUserServiceServer) ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeUserCountry not implemented")
return nil, status.Error(codes.Unimplemented, "method ChangeUserCountry not implemented")
}
func (UnimplementedUserServiceServer) AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminChangeUserCountry not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminChangeUserCountry not implemented")
}
func (UnimplementedUserServiceServer) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetUserStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetUserStatus not implemented")
}
func (UnimplementedUserServiceServer) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateManagerUserBlock not implemented")
}
func (UnimplementedUserServiceServer) ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListManagerUserBlocks not implemented")
}
func (UnimplementedUserServiceServer) UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UnblockManagerUser not implemented")
}
func (UnimplementedUserServiceServer) CompleteOnboarding(context.Context, *CompleteOnboardingRequest) (*CompleteOnboardingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompleteOnboarding not implemented")
return nil, status.Error(codes.Unimplemented, "method CompleteOnboarding not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
@ -240,7 +288,7 @@ type UnsafeUserServiceServer interface {
}
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
// If the following call pancis, it indicates UnimplementedUserServiceServer was
// If the following call panics, it indicates UnimplementedUserServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -430,6 +478,60 @@ func _UserService_SetUserStatus_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _UserService_CreateManagerUserBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateManagerUserBlockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).CreateManagerUserBlock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_CreateManagerUserBlock_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).CreateManagerUserBlock(ctx, req.(*CreateManagerUserBlockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_ListManagerUserBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListManagerUserBlocksRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListManagerUserBlocks(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListManagerUserBlocks_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListManagerUserBlocks(ctx, req.(*ListManagerUserBlocksRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_UnblockManagerUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnblockManagerUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).UnblockManagerUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_UnblockManagerUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).UnblockManagerUser(ctx, req.(*UnblockManagerUserRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_CompleteOnboarding_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CompleteOnboardingRequest)
if err := dec(in); err != nil {
@ -495,6 +597,18 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetUserStatus",
Handler: _UserService_SetUserStatus_Handler,
},
{
MethodName: "CreateManagerUserBlock",
Handler: _UserService_CreateManagerUserBlock_Handler,
},
{
MethodName: "ListManagerUserBlocks",
Handler: _UserService_ListManagerUserBlocks_Handler,
},
{
MethodName: "UnblockManagerUser",
Handler: _UserService_UnblockManagerUser_Handler,
},
{
MethodName: "CompleteOnboarding",
Handler: _UserService_CompleteOnboarding_Handler,
@ -683,37 +797,37 @@ type UserSocialServiceServer interface {
type UnimplementedUserSocialServiceServer struct{}
func (UnimplementedUserSocialServiceServer) RecordProfileVisit(context.Context, *RecordProfileVisitRequest) (*RecordProfileVisitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordProfileVisit not implemented")
return nil, status.Error(codes.Unimplemented, "method RecordProfileVisit not implemented")
}
func (UnimplementedUserSocialServiceServer) ListProfileVisitors(context.Context, *ListProfileVisitorsRequest) (*ListProfileVisitorsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProfileVisitors not implemented")
return nil, status.Error(codes.Unimplemented, "method ListProfileVisitors not implemented")
}
func (UnimplementedUserSocialServiceServer) FollowUser(context.Context, *FollowUserRequest) (*FollowUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FollowUser not implemented")
return nil, status.Error(codes.Unimplemented, "method FollowUser not implemented")
}
func (UnimplementedUserSocialServiceServer) UnfollowUser(context.Context, *UnfollowUserRequest) (*UnfollowUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnfollowUser not implemented")
return nil, status.Error(codes.Unimplemented, "method UnfollowUser not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFollowing(context.Context, *ListFollowingRequest) (*ListFollowingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFollowing not implemented")
return nil, status.Error(codes.Unimplemented, "method ListFollowing not implemented")
}
func (UnimplementedUserSocialServiceServer) ApplyFriend(context.Context, *ApplyFriendRequest) (*ApplyFriendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyFriend not implemented")
return nil, status.Error(codes.Unimplemented, "method ApplyFriend not implemented")
}
func (UnimplementedUserSocialServiceServer) AcceptFriendApplication(context.Context, *AcceptFriendApplicationRequest) (*AcceptFriendApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AcceptFriendApplication not implemented")
return nil, status.Error(codes.Unimplemented, "method AcceptFriendApplication not implemented")
}
func (UnimplementedUserSocialServiceServer) DeleteFriend(context.Context, *DeleteFriendRequest) (*DeleteFriendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteFriend not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteFriend not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFriends(context.Context, *ListFriendsRequest) (*ListFriendsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFriends not implemented")
return nil, status.Error(codes.Unimplemented, "method ListFriends not implemented")
}
func (UnimplementedUserSocialServiceServer) ListFriendApplications(context.Context, *ListFriendApplicationsRequest) (*ListFriendApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFriendApplications not implemented")
return nil, status.Error(codes.Unimplemented, "method ListFriendApplications not implemented")
}
func (UnimplementedUserSocialServiceServer) SubmitReport(context.Context, *SubmitReportRequest) (*SubmitReportResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SubmitReport not implemented")
return nil, status.Error(codes.Unimplemented, "method SubmitReport not implemented")
}
func (UnimplementedUserSocialServiceServer) mustEmbedUnimplementedUserSocialServiceServer() {}
func (UnimplementedUserSocialServiceServer) testEmbeddedByValue() {}
@ -726,7 +840,7 @@ type UnsafeUserSocialServiceServer interface {
}
func RegisterUserSocialServiceServer(s grpc.ServiceRegistrar, srv UserSocialServiceServer) {
// If the following call pancis, it indicates UnimplementedUserSocialServiceServer was
// If the following call panics, it indicates UnimplementedUserSocialServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -991,10 +1105,13 @@ var UserSocialService_ServiceDesc = grpc.ServiceDesc{
}
const (
UserCPService_ListCPApplications_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPApplications"
UserCPService_AcceptCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/AcceptCPApplication"
UserCPService_RejectCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/RejectCPApplication"
UserCPService_ListCPRelationships_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPRelationships"
UserCPService_ListCPApplications_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPApplications"
UserCPService_AcceptCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/AcceptCPApplication"
UserCPService_RejectCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/RejectCPApplication"
UserCPService_ListCPRelationships_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPRelationships"
UserCPService_PrepareBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/PrepareBreakCPRelationship"
UserCPService_ConfirmBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/ConfirmBreakCPRelationship"
UserCPService_CancelBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/CancelBreakCPRelationship"
)
// UserCPServiceClient is the client API for UserCPService service.
@ -1007,6 +1124,9 @@ type UserCPServiceClient interface {
AcceptCPApplication(ctx context.Context, in *AcceptCPApplicationRequest, opts ...grpc.CallOption) (*AcceptCPApplicationResponse, error)
RejectCPApplication(ctx context.Context, in *RejectCPApplicationRequest, opts ...grpc.CallOption) (*RejectCPApplicationResponse, error)
ListCPRelationships(ctx context.Context, in *ListCPRelationshipsRequest, opts ...grpc.CallOption) (*ListCPRelationshipsResponse, error)
PrepareBreakCPRelationship(ctx context.Context, in *PrepareBreakCPRelationshipRequest, opts ...grpc.CallOption) (*PrepareBreakCPRelationshipResponse, error)
ConfirmBreakCPRelationship(ctx context.Context, in *ConfirmBreakCPRelationshipRequest, opts ...grpc.CallOption) (*ConfirmBreakCPRelationshipResponse, error)
CancelBreakCPRelationship(ctx context.Context, in *CancelBreakCPRelationshipRequest, opts ...grpc.CallOption) (*CancelBreakCPRelationshipResponse, error)
}
type userCPServiceClient struct {
@ -1057,6 +1177,36 @@ func (c *userCPServiceClient) ListCPRelationships(ctx context.Context, in *ListC
return out, nil
}
func (c *userCPServiceClient) PrepareBreakCPRelationship(ctx context.Context, in *PrepareBreakCPRelationshipRequest, opts ...grpc.CallOption) (*PrepareBreakCPRelationshipResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PrepareBreakCPRelationshipResponse)
err := c.cc.Invoke(ctx, UserCPService_PrepareBreakCPRelationship_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userCPServiceClient) ConfirmBreakCPRelationship(ctx context.Context, in *ConfirmBreakCPRelationshipRequest, opts ...grpc.CallOption) (*ConfirmBreakCPRelationshipResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ConfirmBreakCPRelationshipResponse)
err := c.cc.Invoke(ctx, UserCPService_ConfirmBreakCPRelationship_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userCPServiceClient) CancelBreakCPRelationship(ctx context.Context, in *CancelBreakCPRelationshipRequest, opts ...grpc.CallOption) (*CancelBreakCPRelationshipResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CancelBreakCPRelationshipResponse)
err := c.cc.Invoke(ctx, UserCPService_CancelBreakCPRelationship_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserCPServiceServer is the server API for UserCPService service.
// All implementations must embed UnimplementedUserCPServiceServer
// for forward compatibility.
@ -1067,6 +1217,9 @@ type UserCPServiceServer interface {
AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error)
RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error)
ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error)
PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error)
ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error)
CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error)
mustEmbedUnimplementedUserCPServiceServer()
}
@ -1078,16 +1231,25 @@ type UserCPServiceServer interface {
type UnimplementedUserCPServiceServer struct{}
func (UnimplementedUserCPServiceServer) ListCPApplications(context.Context, *ListCPApplicationsRequest) (*ListCPApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPApplications not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCPApplications not implemented")
}
func (UnimplementedUserCPServiceServer) AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AcceptCPApplication not implemented")
return nil, status.Error(codes.Unimplemented, "method AcceptCPApplication not implemented")
}
func (UnimplementedUserCPServiceServer) RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RejectCPApplication not implemented")
return nil, status.Error(codes.Unimplemented, "method RejectCPApplication not implemented")
}
func (UnimplementedUserCPServiceServer) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPRelationships not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCPRelationships not implemented")
}
func (UnimplementedUserCPServiceServer) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ConfirmBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CancelBreakCPRelationship not implemented")
}
func (UnimplementedUserCPServiceServer) mustEmbedUnimplementedUserCPServiceServer() {}
func (UnimplementedUserCPServiceServer) testEmbeddedByValue() {}
@ -1100,7 +1262,7 @@ type UnsafeUserCPServiceServer interface {
}
func RegisterUserCPServiceServer(s grpc.ServiceRegistrar, srv UserCPServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCPServiceServer was
// If the following call panics, it indicates UnimplementedUserCPServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1182,6 +1344,60 @@ func _UserCPService_ListCPRelationships_Handler(srv interface{}, ctx context.Con
return interceptor(ctx, in, info, handler)
}
func _UserCPService_PrepareBreakCPRelationship_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PrepareBreakCPRelationshipRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserCPServiceServer).PrepareBreakCPRelationship(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserCPService_PrepareBreakCPRelationship_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserCPServiceServer).PrepareBreakCPRelationship(ctx, req.(*PrepareBreakCPRelationshipRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserCPService_ConfirmBreakCPRelationship_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConfirmBreakCPRelationshipRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserCPServiceServer).ConfirmBreakCPRelationship(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserCPService_ConfirmBreakCPRelationship_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserCPServiceServer).ConfirmBreakCPRelationship(ctx, req.(*ConfirmBreakCPRelationshipRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserCPService_CancelBreakCPRelationship_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CancelBreakCPRelationshipRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserCPServiceServer).CancelBreakCPRelationship(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserCPService_CancelBreakCPRelationship_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserCPServiceServer).CancelBreakCPRelationship(ctx, req.(*CancelBreakCPRelationshipRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserCPService_ServiceDesc is the grpc.ServiceDesc for UserCPService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -1205,6 +1421,18 @@ var UserCPService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListCPRelationships",
Handler: _UserCPService_ListCPRelationships_Handler,
},
{
MethodName: "PrepareBreakCPRelationship",
Handler: _UserCPService_PrepareBreakCPRelationship_Handler,
},
{
MethodName: "ConfirmBreakCPRelationship",
Handler: _UserCPService_ConfirmBreakCPRelationship_Handler,
},
{
MethodName: "CancelBreakCPRelationship",
Handler: _UserCPService_CancelBreakCPRelationship_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/user/v1/user.proto",
@ -1259,7 +1487,7 @@ type UserCPInternalServiceServer interface {
type UnimplementedUserCPInternalServiceServer struct{}
func (UnimplementedUserCPInternalServiceServer) ConsumeRoomGiftCPEvent(context.Context, *ConsumeRoomGiftCPEventRequest) (*ConsumeRoomGiftCPEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented")
return nil, status.Error(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented")
}
func (UnimplementedUserCPInternalServiceServer) mustEmbedUnimplementedUserCPInternalServiceServer() {}
func (UnimplementedUserCPInternalServiceServer) testEmbeddedByValue() {}
@ -1272,7 +1500,7 @@ type UnsafeUserCPInternalServiceServer interface {
}
func RegisterUserCPInternalServiceServer(s grpc.ServiceRegistrar, srv UserCPInternalServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCPInternalServiceServer was
// If the following call panics, it indicates UnimplementedUserCPInternalServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1320,6 +1548,7 @@ const (
UserCronService_ProcessLoginIPRiskBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessLoginIPRiskBatch"
UserCronService_ProcessRegionRebuildBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessRegionRebuildBatch"
UserCronService_CompensateMicOpenSessions_FullMethodName = "/hyapp.user.v1.UserCronService/CompensateMicOpenSessions"
UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks"
)
// UserCronServiceClient is the client API for UserCronService service.
@ -1331,6 +1560,7 @@ type UserCronServiceClient interface {
ProcessLoginIPRiskBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
ProcessRegionRebuildBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
CompensateMicOpenSessions(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
}
type userCronServiceClient struct {
@ -1371,6 +1601,16 @@ func (c *userCronServiceClient) CompensateMicOpenSessions(ctx context.Context, i
return out, nil
}
func (c *userCronServiceClient) ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CronBatchResponse)
err := c.cc.Invoke(ctx, UserCronService_ExpireManagerUserBlocks_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserCronServiceServer is the server API for UserCronService service.
// All implementations must embed UnimplementedUserCronServiceServer
// for forward compatibility.
@ -1380,6 +1620,7 @@ type UserCronServiceServer interface {
ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
mustEmbedUnimplementedUserCronServiceServer()
}
@ -1391,13 +1632,16 @@ type UserCronServiceServer interface {
type UnimplementedUserCronServiceServer struct{}
func (UnimplementedUserCronServiceServer) ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented")
}
func (UnimplementedUserCronServiceServer) ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented")
}
func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompensateMicOpenSessions not implemented")
return nil, status.Error(codes.Unimplemented, "method CompensateMicOpenSessions not implemented")
}
func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented")
}
func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {}
func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {}
@ -1410,7 +1654,7 @@ type UnsafeUserCronServiceServer interface {
}
func RegisterUserCronServiceServer(s grpc.ServiceRegistrar, srv UserCronServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCronServiceServer was
// If the following call panics, it indicates UnimplementedUserCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1474,6 +1718,24 @@ func _UserCronService_CompensateMicOpenSessions_Handler(srv interface{}, ctx con
return interceptor(ctx, in, info, handler)
}
func _UserCronService_ExpireManagerUserBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CronBatchRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserCronServiceServer).ExpireManagerUserBlocks(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserCronService_ExpireManagerUserBlocks_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserCronServiceServer).ExpireManagerUserBlocks(ctx, req.(*CronBatchRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserCronService_ServiceDesc is the grpc.ServiceDesc for UserCronService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -1493,6 +1755,10 @@ var UserCronService_ServiceDesc = grpc.ServiceDesc{
MethodName: "CompensateMicOpenSessions",
Handler: _UserCronService_CompensateMicOpenSessions_Handler,
},
{
MethodName: "ExpireManagerUserBlocks",
Handler: _UserCronService_ExpireManagerUserBlocks_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/user/v1/user.proto",
@ -1560,10 +1826,10 @@ type UserDeviceServiceServer interface {
type UnimplementedUserDeviceServiceServer struct{}
func (UnimplementedUserDeviceServiceServer) BindPushToken(context.Context, *BindPushTokenRequest) (*BindPushTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BindPushToken not implemented")
return nil, status.Error(codes.Unimplemented, "method BindPushToken not implemented")
}
func (UnimplementedUserDeviceServiceServer) DeletePushToken(context.Context, *DeletePushTokenRequest) (*DeletePushTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeletePushToken not implemented")
return nil, status.Error(codes.Unimplemented, "method DeletePushToken not implemented")
}
func (UnimplementedUserDeviceServiceServer) mustEmbedUnimplementedUserDeviceServiceServer() {}
func (UnimplementedUserDeviceServiceServer) testEmbeddedByValue() {}
@ -1576,7 +1842,7 @@ type UnsafeUserDeviceServiceServer interface {
}
func RegisterUserDeviceServiceServer(s grpc.ServiceRegistrar, srv UserDeviceServiceServer) {
// If the following call pancis, it indicates UnimplementedUserDeviceServiceServer was
// If the following call panics, it indicates UnimplementedUserDeviceServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1691,7 +1957,7 @@ type AppRegistryServiceServer interface {
type UnimplementedAppRegistryServiceServer struct{}
func (UnimplementedAppRegistryServiceServer) ResolveApp(context.Context, *ResolveAppRequest) (*ResolveAppResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResolveApp not implemented")
return nil, status.Error(codes.Unimplemented, "method ResolveApp not implemented")
}
func (UnimplementedAppRegistryServiceServer) mustEmbedUnimplementedAppRegistryServiceServer() {}
func (UnimplementedAppRegistryServiceServer) testEmbeddedByValue() {}
@ -1704,7 +1970,7 @@ type UnsafeAppRegistryServiceServer interface {
}
func RegisterAppRegistryServiceServer(s grpc.ServiceRegistrar, srv AppRegistryServiceServer) {
// If the following call pancis, it indicates UnimplementedAppRegistryServiceServer was
// If the following call panics, it indicates UnimplementedAppRegistryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1812,10 +2078,10 @@ type CountryAdminServiceServer interface {
type UnimplementedCountryAdminServiceServer struct{}
func (UnimplementedCountryAdminServiceServer) ListCountries(context.Context, *ListCountriesRequest) (*ListCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCountries not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCountries not implemented")
}
func (UnimplementedCountryAdminServiceServer) UpdateCountry(context.Context, *UpdateCountryRequest) (*CountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateCountry not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateCountry not implemented")
}
func (UnimplementedCountryAdminServiceServer) mustEmbedUnimplementedCountryAdminServiceServer() {}
func (UnimplementedCountryAdminServiceServer) testEmbeddedByValue() {}
@ -1828,7 +2094,7 @@ type UnsafeCountryAdminServiceServer interface {
}
func RegisterCountryAdminServiceServer(s grpc.ServiceRegistrar, srv CountryAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedCountryAdminServiceServer was
// If the following call panics, it indicates UnimplementedCountryAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -1956,10 +2222,10 @@ type CountryQueryServiceServer interface {
type UnimplementedCountryQueryServiceServer struct{}
func (UnimplementedCountryQueryServiceServer) ListRegistrationCountries(context.Context, *ListRegistrationCountriesRequest) (*ListRegistrationCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRegistrationCountries not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRegistrationCountries not implemented")
}
func (UnimplementedCountryQueryServiceServer) ListLoginRiskBlockedCountries(context.Context, *ListLoginRiskBlockedCountriesRequest) (*ListLoginRiskBlockedCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented")
return nil, status.Error(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented")
}
func (UnimplementedCountryQueryServiceServer) mustEmbedUnimplementedCountryQueryServiceServer() {}
func (UnimplementedCountryQueryServiceServer) testEmbeddedByValue() {}
@ -1972,7 +2238,7 @@ type UnsafeCountryQueryServiceServer interface {
}
func RegisterCountryQueryServiceServer(s grpc.ServiceRegistrar, srv CountryQueryServiceServer) {
// If the following call pancis, it indicates UnimplementedCountryQueryServiceServer was
// If the following call panics, it indicates UnimplementedCountryQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2128,16 +2394,16 @@ type RegionAdminServiceServer interface {
type UnimplementedRegionAdminServiceServer struct{}
func (UnimplementedRegionAdminServiceServer) ListRegions(context.Context, *ListRegionsRequest) (*ListRegionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRegions not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRegions not implemented")
}
func (UnimplementedRegionAdminServiceServer) GetRegion(context.Context, *GetRegionRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRegion not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRegion not implemented")
}
func (UnimplementedRegionAdminServiceServer) UpdateRegion(context.Context, *UpdateRegionRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegion not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRegion not implemented")
}
func (UnimplementedRegionAdminServiceServer) ReplaceRegionCountries(context.Context, *ReplaceRegionCountriesRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReplaceRegionCountries not implemented")
return nil, status.Error(codes.Unimplemented, "method ReplaceRegionCountries not implemented")
}
func (UnimplementedRegionAdminServiceServer) mustEmbedUnimplementedRegionAdminServiceServer() {}
func (UnimplementedRegionAdminServiceServer) testEmbeddedByValue() {}
@ -2150,7 +2416,7 @@ type UnsafeRegionAdminServiceServer interface {
}
func RegisterRegionAdminServiceServer(s grpc.ServiceRegistrar, srv RegionAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedRegionAdminServiceServer was
// If the following call panics, it indicates UnimplementedRegionAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2361,19 +2627,19 @@ type UserIdentityServiceServer interface {
type UnimplementedUserIdentityServiceServer struct{}
func (UnimplementedUserIdentityServiceServer) GetUserIdentity(context.Context, *GetUserIdentityRequest) (*GetUserIdentityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserIdentity not implemented")
return nil, status.Error(codes.Unimplemented, "method GetUserIdentity not implemented")
}
func (UnimplementedUserIdentityServiceServer) ResolveDisplayUserID(context.Context, *ResolveDisplayUserIDRequest) (*ResolveDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResolveDisplayUserID not implemented")
return nil, status.Error(codes.Unimplemented, "method ResolveDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ChangeDisplayUserID(context.Context, *ChangeDisplayUserIDRequest) (*ChangeDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeDisplayUserID not implemented")
return nil, status.Error(codes.Unimplemented, "method ChangeDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented")
return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented")
return nil, status.Error(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented")
}
func (UnimplementedUserIdentityServiceServer) mustEmbedUnimplementedUserIdentityServiceServer() {}
func (UnimplementedUserIdentityServiceServer) testEmbeddedByValue() {}
@ -2386,7 +2652,7 @@ type UnsafeUserIdentityServiceServer interface {
}
func RegisterUserIdentityServiceServer(s grpc.ServiceRegistrar, srv UserIdentityServiceServer) {
// If the following call pancis, it indicates UnimplementedUserIdentityServiceServer was
// If the following call panics, it indicates UnimplementedUserIdentityServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1264,6 +1264,22 @@ message PurchaseVipResponse {
repeated VipRewardItem reward_items = 6;
}
message DebitCPBreakupFeeRequest {
string command_id = 1;
string app_code = 2;
int64 user_id = 3;
string relationship_id = 4;
string relation_type = 5;
int64 amount = 6;
}
message DebitCPBreakupFeeResponse {
string transaction_id = 1;
int64 coin_spent = 2;
int64 coin_balance_after = 3;
AssetBalance balance = 4;
}
message GrantVipRequest {
string command_id = 1;
string app_code = 2;
@ -1657,6 +1673,7 @@ service WalletService {
rpc ListVipPackages(ListVipPackagesRequest) returns (ListVipPackagesResponse);
rpc GetMyVip(GetMyVipRequest) returns (GetMyVipResponse);
rpc PurchaseVip(PurchaseVipRequest) returns (PurchaseVipResponse);
rpc DebitCPBreakupFee(DebitCPBreakupFeeRequest) returns (DebitCPBreakupFeeResponse);
rpc GrantVip(GrantVipRequest) returns (GrantVipResponse);
rpc ListAdminVipLevels(ListAdminVipLevelsRequest) returns (ListAdminVipLevelsResponse);
rpc UpdateAdminVipLevels(UpdateAdminVipLevelsRequest) returns (UpdateAdminVipLevelsResponse);

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0
// source: proto/wallet/v1/wallet.proto
@ -93,13 +93,13 @@ type WalletCronServiceServer interface {
type UnimplementedWalletCronServiceServer struct{}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryDailySettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryHalfMonthSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented")
return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented")
}
func (UnimplementedWalletCronServiceServer) mustEmbedUnimplementedWalletCronServiceServer() {}
func (UnimplementedWalletCronServiceServer) testEmbeddedByValue() {}
@ -112,7 +112,7 @@ type UnsafeWalletCronServiceServer interface {
}
func RegisterWalletCronServiceServer(s grpc.ServiceRegistrar, srv WalletCronServiceServer) {
// If the following call pancis, it indicates UnimplementedWalletCronServiceServer was
// If the following call panics, it indicates UnimplementedWalletCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -254,6 +254,7 @@ const (
WalletService_ListVipPackages_FullMethodName = "/hyapp.wallet.v1.WalletService/ListVipPackages"
WalletService_GetMyVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GetMyVip"
WalletService_PurchaseVip_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseVip"
WalletService_DebitCPBreakupFee_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitCPBreakupFee"
WalletService_GrantVip_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantVip"
WalletService_ListAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/ListAdminVipLevels"
WalletService_UpdateAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateAdminVipLevels"
@ -330,6 +331,7 @@ type WalletServiceClient interface {
ListVipPackages(ctx context.Context, in *ListVipPackagesRequest, opts ...grpc.CallOption) (*ListVipPackagesResponse, error)
GetMyVip(ctx context.Context, in *GetMyVipRequest, opts ...grpc.CallOption) (*GetMyVipResponse, error)
PurchaseVip(ctx context.Context, in *PurchaseVipRequest, opts ...grpc.CallOption) (*PurchaseVipResponse, error)
DebitCPBreakupFee(ctx context.Context, in *DebitCPBreakupFeeRequest, opts ...grpc.CallOption) (*DebitCPBreakupFeeResponse, error)
GrantVip(ctx context.Context, in *GrantVipRequest, opts ...grpc.CallOption) (*GrantVipResponse, error)
ListAdminVipLevels(ctx context.Context, in *ListAdminVipLevelsRequest, opts ...grpc.CallOption) (*ListAdminVipLevelsResponse, error)
UpdateAdminVipLevels(ctx context.Context, in *UpdateAdminVipLevelsRequest, opts ...grpc.CallOption) (*UpdateAdminVipLevelsResponse, error)
@ -885,6 +887,16 @@ func (c *walletServiceClient) PurchaseVip(ctx context.Context, in *PurchaseVipRe
return out, nil
}
func (c *walletServiceClient) DebitCPBreakupFee(ctx context.Context, in *DebitCPBreakupFeeRequest, opts ...grpc.CallOption) (*DebitCPBreakupFeeResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DebitCPBreakupFeeResponse)
err := c.cc.Invoke(ctx, WalletService_DebitCPBreakupFee_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) GrantVip(ctx context.Context, in *GrantVipRequest, opts ...grpc.CallOption) (*GrantVipResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GrantVipResponse)
@ -1094,6 +1106,7 @@ type WalletServiceServer interface {
ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error)
GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error)
PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error)
DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error)
GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error)
ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error)
UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error)
@ -1120,208 +1133,211 @@ type WalletServiceServer interface {
type UnimplementedWalletServiceServer struct{}
func (UnimplementedWalletServiceServer) DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitGift not implemented")
return nil, status.Error(codes.Unimplemented, "method DebitGift not implemented")
}
func (UnimplementedWalletServiceServer) BatchDebitGift(context.Context, *BatchDebitGiftRequest) (*BatchDebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchDebitGift not implemented")
return nil, status.Error(codes.Unimplemented, "method BatchDebitGift not implemented")
}
func (UnimplementedWalletServiceServer) GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBalances not implemented")
return nil, status.Error(codes.Unimplemented, "method GetBalances not implemented")
}
func (UnimplementedWalletServiceServer) GetActiveHostSalaryPolicy(context.Context, *GetActiveHostSalaryPolicyRequest) (*GetActiveHostSalaryPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented")
return nil, status.Error(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented")
}
func (UnimplementedWalletServiceServer) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostSalaryProgress not implemented")
return nil, status.Error(codes.Unimplemented, "method GetHostSalaryProgress not implemented")
}
func (UnimplementedWalletServiceServer) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditAsset not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminCreditAsset not implemented")
}
func (UnimplementedWalletServiceServer) AdminCreditCoinSellerStock(context.Context, *AdminCreditCoinSellerStockRequest) (*AdminCreditCoinSellerStockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented")
return nil, status.Error(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented")
}
func (UnimplementedWalletServiceServer) TransferCoinFromSeller(context.Context, *TransferCoinFromSellerRequest) (*TransferCoinFromSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferCoinFromSeller not implemented")
return nil, status.Error(codes.Unimplemented, "method TransferCoinFromSeller not implemented")
}
func (UnimplementedWalletServiceServer) ListCoinSellerSalaryExchangeRateTiers(context.Context, *ListCoinSellerSalaryExchangeRateTiersRequest) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented")
return nil, status.Error(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented")
}
func (UnimplementedWalletServiceServer) ExchangeSalaryToCoin(context.Context, *ExchangeSalaryToCoinRequest) (*ExchangeSalaryToCoinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented")
return nil, status.Error(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented")
}
func (UnimplementedWalletServiceServer) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented")
return nil, status.Error(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented")
}
func (UnimplementedWalletServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResources not implemented")
return nil, status.Error(codes.Unimplemented, "method ListResources not implemented")
}
func (UnimplementedWalletServiceServer) GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented")
return nil, status.Error(codes.Unimplemented, "method GetResource not implemented")
}
func (UnimplementedWalletServiceServer) CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateResource not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateResource not implemented")
}
func (UnimplementedWalletServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateResource not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetResourceStatus not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGroups not implemented")
return nil, status.Error(codes.Unimplemented, "method ListResourceGroups not implemented")
}
func (UnimplementedWalletServiceServer) GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResourceGroup not implemented")
return nil, status.Error(codes.Unimplemented, "method GetResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateResourceGroup not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResourceGroup not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceGroupStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetResourceGroupStatus not implemented")
}
func (UnimplementedWalletServiceServer) ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGiftConfigs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListGiftConfigs not implemented")
}
func (UnimplementedWalletServiceServer) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGiftTypeConfigs not implemented")
return nil, status.Error(codes.Unimplemented, "method ListGiftTypeConfigs not implemented")
}
func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateGiftConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateGiftConfig not implemented")
}
func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateGiftConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateGiftConfig not implemented")
}
func (UnimplementedWalletServiceServer) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGiftConfigStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetGiftConfigStatus not implemented")
}
func (UnimplementedWalletServiceServer) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented")
}
func (UnimplementedWalletServiceServer) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantResource not implemented")
return nil, status.Error(codes.Unimplemented, "method GrantResource not implemented")
}
func (UnimplementedWalletServiceServer) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantResourceGroup not implemented")
return nil, status.Error(codes.Unimplemented, "method GrantResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserResources not implemented")
return nil, status.Error(codes.Unimplemented, "method ListUserResources not implemented")
}
func (UnimplementedWalletServiceServer) EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EquipUserResource not implemented")
return nil, status.Error(codes.Unimplemented, "method EquipUserResource not implemented")
}
func (UnimplementedWalletServiceServer) UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnequipUserResource not implemented")
return nil, status.Error(codes.Unimplemented, "method UnequipUserResource not implemented")
}
func (UnimplementedWalletServiceServer) BatchGetUserEquippedResources(context.Context, *BatchGetUserEquippedResourcesRequest) (*BatchGetUserEquippedResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented")
return nil, status.Error(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceGrants(context.Context, *ListResourceGrantsRequest) (*ListResourceGrantsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGrants not implemented")
return nil, status.Error(codes.Unimplemented, "method ListResourceGrants not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceShopItems(context.Context, *ListResourceShopItemsRequest) (*ListResourceShopItemsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceShopItems not implemented")
return nil, status.Error(codes.Unimplemented, "method ListResourceShopItems not implemented")
}
func (UnimplementedWalletServiceServer) UpsertResourceShopItems(context.Context, *UpsertResourceShopItemsRequest) (*UpsertResourceShopItemsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertResourceShopItems not implemented")
return nil, status.Error(codes.Unimplemented, "method UpsertResourceShopItems not implemented")
}
func (UnimplementedWalletServiceServer) SetResourceShopItemStatus(context.Context, *SetResourceShopItemStatusRequest) (*ResourceShopItemResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceShopItemStatus not implemented")
return nil, status.Error(codes.Unimplemented, "method SetResourceShopItemStatus not implemented")
}
func (UnimplementedWalletServiceServer) PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PurchaseResourceShopItem not implemented")
return nil, status.Error(codes.Unimplemented, "method PurchaseResourceShopItem not implemented")
}
func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeBills not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRechargeBills not implemented")
}
func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWalletOverview not implemented")
return nil, status.Error(codes.Unimplemented, "method GetWalletOverview not implemented")
}
func (UnimplementedWalletServiceServer) GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWalletValueSummary not implemented")
return nil, status.Error(codes.Unimplemented, "method GetWalletValueSummary not implemented")
}
func (UnimplementedWalletServiceServer) GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserGiftWall not implemented")
return nil, status.Error(codes.Unimplemented, "method GetUserGiftWall not implemented")
}
func (UnimplementedWalletServiceServer) ListRechargeProducts(context.Context, *ListRechargeProductsRequest) (*ListRechargeProductsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeProducts not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRechargeProducts not implemented")
}
func (UnimplementedWalletServiceServer) ConfirmGooglePayment(context.Context, *ConfirmGooglePaymentRequest) (*ConfirmGooglePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmGooglePayment not implemented")
return nil, status.Error(codes.Unimplemented, "method ConfirmGooglePayment not implemented")
}
func (UnimplementedWalletServiceServer) ListAdminRechargeProducts(context.Context, *ListAdminRechargeProductsRequest) (*ListAdminRechargeProductsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAdminRechargeProducts not implemented")
return nil, status.Error(codes.Unimplemented, "method ListAdminRechargeProducts not implemented")
}
func (UnimplementedWalletServiceServer) CreateRechargeProduct(context.Context, *CreateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRechargeProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) UpdateRechargeProduct(context.Context, *UpdateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRechargeProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteRechargeProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteRechargeProduct not implemented")
}
func (UnimplementedWalletServiceServer) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented")
}
func (UnimplementedWalletServiceServer) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWalletTransactions not implemented")
return nil, status.Error(codes.Unimplemented, "method ListWalletTransactions not implemented")
}
func (UnimplementedWalletServiceServer) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListVipPackages not implemented")
return nil, status.Error(codes.Unimplemented, "method ListVipPackages not implemented")
}
func (UnimplementedWalletServiceServer) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyVip not implemented")
return nil, status.Error(codes.Unimplemented, "method GetMyVip not implemented")
}
func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PurchaseVip not implemented")
return nil, status.Error(codes.Unimplemented, "method PurchaseVip not implemented")
}
func (UnimplementedWalletServiceServer) DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DebitCPBreakupFee not implemented")
}
func (UnimplementedWalletServiceServer) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantVip not implemented")
return nil, status.Error(codes.Unimplemented, "method GrantVip not implemented")
}
func (UnimplementedWalletServiceServer) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAdminVipLevels not implemented")
return nil, status.Error(codes.Unimplemented, "method ListAdminVipLevels not implemented")
}
func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipLevels not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateAdminVipLevels not implemented")
}
func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditTaskReward not implemented")
return nil, status.Error(codes.Unimplemented, "method CreditTaskReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditLuckyGiftReward not implemented")
return nil, status.Error(codes.Unimplemented, "method CreditLuckyGiftReward not implemented")
}
func (UnimplementedWalletServiceServer) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented")
return nil, status.Error(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented")
}
func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyGameCoinChange not implemented")
return nil, status.Error(codes.Unimplemented, "method ApplyGameCoinChange not implemented")
}
func (UnimplementedWalletServiceServer) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacketConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRedPacketConfig not implemented")
}
func (UnimplementedWalletServiceServer) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRedPacketConfig not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateRedPacketConfig not implemented")
}
func (UnimplementedWalletServiceServer) CreateRedPacket(context.Context, *CreateRedPacketRequest) (*CreateRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRedPacket not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ClaimRedPacket(context.Context, *ClaimRedPacketRequest) (*ClaimRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimRedPacket not implemented")
return nil, status.Error(codes.Unimplemented, "method ClaimRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ListRedPackets(context.Context, *ListRedPacketsRequest) (*ListRedPacketsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRedPackets not implemented")
return nil, status.Error(codes.Unimplemented, "method ListRedPackets not implemented")
}
func (UnimplementedWalletServiceServer) GetRedPacket(context.Context, *GetRedPacketRequest) (*GetRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacket not implemented")
return nil, status.Error(codes.Unimplemented, "method GetRedPacket not implemented")
}
func (UnimplementedWalletServiceServer) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpireRedPackets not implemented")
return nil, status.Error(codes.Unimplemented, "method ExpireRedPackets not implemented")
}
func (UnimplementedWalletServiceServer) RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RetryRedPacketRefund not implemented")
return nil, status.Error(codes.Unimplemented, "method RetryRedPacketRefund not implemented")
}
func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {}
func (UnimplementedWalletServiceServer) testEmbeddedByValue() {}
@ -1334,7 +1350,7 @@ type UnsafeWalletServiceServer interface {
}
func RegisterWalletServiceServer(s grpc.ServiceRegistrar, srv WalletServiceServer) {
// If the following call pancis, it indicates UnimplementedWalletServiceServer was
// If the following call panics, it indicates UnimplementedWalletServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -2298,6 +2314,24 @@ func _WalletService_PurchaseVip_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _WalletService_DebitCPBreakupFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DebitCPBreakupFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).DebitCPBreakupFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_DebitCPBreakupFee_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).DebitCPBreakupFee(ctx, req.(*DebitCPBreakupFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_GrantVip_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GrantVipRequest)
if err := dec(in); err != nil {
@ -2787,6 +2821,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "PurchaseVip",
Handler: _WalletService_PurchaseVip_Handler,
},
{
MethodName: "DebitCPBreakupFee",
Handler: _WalletService_DebitCPBreakupFee_Handler,
},
{
MethodName: "GrantVip",
Handler: _WalletService_GrantVip_Handler,

View File

@ -12,11 +12,12 @@
规则:
- 每个用户同时只能有一个 active 关系,不按类型拆数量,也不需要后台配置数量。
- CP 关系每个用户最多 1 个,不提供可调数量。
- 兄弟和姐妹的每人 active 数量由后台 CP 配置控制。
- A 和 B 同时只能存在一种 active 关系。
- A 可以给 B 发送 `cp``brother``sister` 多种 CP 礼物,形成多条 pending 申请。
- B 接受其中一个申请后,同一对用户其他 pending 申请会被置为 `blocked`,不能再接受。
- 申请默认 24 小时过期,当前不提供后台配置入口
- 申请默认 24 小时过期。
- 不提供佩戴 CP 功能;资料卡如果要展示关系,直接展示用户当前 active 关系。
## 2. 服务归属
@ -118,6 +119,7 @@ sequenceDiagram
U-->>N: UserCPApplicationAccepted MQ
U-->>N: UserCPRelationshipCreated MQ
N->>IM: B -> A C2C cp_application_accepted
N->>IM: A -> B C2C cp_application_accepted
N->>IM: room cp_application_accepted, recipient_user_id=A
N->>IM: region cp_relationship_created
```
@ -128,7 +130,9 @@ sequenceDiagram
- pending 申请过期后不能接受gateway envelope 返回 `CONFLICT`;内部 gRPC 语义是 FailedPrecondition。
- 接受时同时校验 A 和 B 是否已经有任意 active 关系;任一方已有关系就返回 `CONFLICT`
- 同意成功后写 active 关系,关闭同一对用户其他 pending 申请。
- 同意或拒绝都向 A 发送 C2C IM 和房间 IM。
- 同意会发两条 C2C`B -> A` 表示“我同意了你”,`A -> B` 表示“对方已同意你的申请”。
- 同意和拒绝都会向 A 发送房间 IM`recipient_user_id=A` 限定展示对象。
- 拒绝仍只向 A 发送一条 C2C 和一条房间 IM。
- 同意成功后额外发送区域 IM区域取申请来源房间的 `visible_region_id`
## 7. 亲密值和升级
@ -140,11 +144,25 @@ sequenceDiagram
- 普通礼物按 100% 热度值增加亲密值。
- 幸运礼物、超级幸运礼物直接使用钱包回执里的最终 `heat_value`,不重复计算比例。
- 多目标送礼时按每个目标的账务回执分别增加对应关系亲密值。
- 等级范围 1 到 5当前使用 user-service 默认等级规则,不提供后台配置入口
- 等级范围 1 到 5等级亲密值阈值和奖励资源组由后台 CP 配置维护
- 每个等级可绑定奖励资源组,升级时调用 `wallet-service` 给双方发放资源。
- 奖励发放幂等键为 `cp_level_reward:{relationship_id}:{level}:{user_id}`
- App 关系查询返回当前等级阈值、下一等级阈值、距离下一级和进度百分比。
- 资料卡弹窗只能使用 `/api/v1/cp/relationships` 返回的阈值字段展示进度;客户端不能读取后台配置或硬编码等级表。
## 8. App HTTP接口
## 8. 解除关系
用户可以主动解除自己的 active 关系。解除费用由后台 CP 配置按关系类型维护,单位是金币。
规则:
- App 先通过 `/api/v1/cp/relationships` 读取 `breakup_cost_coins` 展示费用。
- 解除接口必须传 `command_id`,同一次解除重试复用同一个 `command_id`
- gateway 先在 user-service 创建解除占位,再调用 wallet-service 扣金币,最后确认关系为 `ended`
- 如果 wallet-service 扣费失败user-service 会取消解除占位,关系仍保持 active。
- 解除成功后不会发送 C2C、房间 IM 或区域 IM客户端使用接口返回和关系列表刷新 UI。
## 9. App HTTP接口
所有接口走 `gateway-service`,响应使用 `{code,message,request_id,data}`
@ -154,6 +172,30 @@ sequenceDiagram
| `POST` | `/api/v1/cp/applications/{application_id}/accept` | 同意申请 |
| `POST` | `/api/v1/cp/applications/{application_id}/reject` | 拒绝申请 |
| `GET` | `/api/v1/cp/relationships` | 查询我的 active 关系 |
| `POST` | `/api/v1/cp/relationships/{relationship_id}/break` | 解除关系并扣金币 |
`/api/v1/cp/relationships` 的 relationship item 包含:
| 字段 | 说明 |
| --- | --- |
| `relationship_id` | 关系 ID |
| `relation_type` | `cp``brother``sister` |
| `status` | 当前为 `active` |
| `me` | 当前用户快照 |
| `partner` | 对方用户快照 |
| `intimacy_value` | 当前亲密值 |
| `level` | 当前等级 |
| `current_level_threshold` | 当前等级起始亲密值 |
| `next_level_threshold` | 下一等级所需亲密值;满级时为 0 |
| `needed_for_next_level` | 距离下一级还需要多少亲密值;满级时为 0 |
| `level_progress_percent` | 当前等级内进度百分比0 到 100 |
| `max_level` | 是否已经没有下一等级 |
| `breakup_cost_coins` | 当前关系解除需要支付的金币数 |
| `formed_at_ms` | 关系形成时间 |
| `ended_at_ms` | 解除时间active 关系为 0 |
| `updated_at_ms` | 最近更新时间 |
这些字段由 user-service 从 `user_cp_level_rules` 计算gateway 只做透传。客户端资料卡不能根据本地表推算这些值。
申请列表参数:
@ -178,41 +220,61 @@ sequenceDiagram
| --- | --- | --- | --- |
| `reason` | string | 否 | 拒绝原因,只进入 IM/outbox payload |
## 9. 数据表
解除请求体:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `command_id` | string | 是 | 解除关系幂等键,同一次重试必须复用 |
解除返回值:
| 字段 | 说明 |
| --- | --- |
| `relationship` | 已解除的关系,`status=ended` |
| `breakup_cost_coins` | 本次实际扣除金币数 |
| `wallet_transaction_id` | 钱包扣费交易 ID费用为 0 时为空 |
| `coin_balance_after` | 扣费后金币余额;费用为 0 时为 0 |
## 10. 数据表
表放在 `user-service` 库,所有表带 `app_code`,时间使用 UTC epoch milliseconds。
| 表 | 说明 |
| --- | --- |
| `user_cp_relation_configs` | 历史兼容表;当前固定 `max_count_per_user=1`,不提供后台配置 |
| `user_cp_level_rules` | 默认等级阈值和奖励资源组 |
| `user_cp_relation_configs` | CP/兄弟/姐妹数量、申请过期和解除费用配置CP 数量服务端固定为 1兄弟/姐妹读取后台配置 |
| `user_cp_level_rules` | 等级阈值和奖励资源组 |
| `user_cp_gift_event_consumption` | `RoomGiftSent` 消费幂等 |
| `user_cp_applications` | CP 关系申请 |
| `user_cp_relationships` | 双人关系事实 |
| `user_cp_relationship_breakups` | 解除关系 pending/confirmed/canceled 占位和幂等事实 |
| `user_cp_level_reward_grants` | 等级奖励发放幂等 |
| `user_outbox` | CP 事件事实 |
关键唯一约束:
- active 关系按用户对唯一:`(app_code, user_a_id, user_b_id, status='active')`
- active 关系按单用户唯一:任一用户已有 active 关系就不能再接受新关系。
- CP active 关系按单用户唯一:任一用户已有 CP 关系就不能再接受新的 CP 关系。
- 兄弟/姐妹 active 关系按后台配置做单用户数量上限校验。
- pending 申请按用户对和类型唯一:`(app_code, requester_user_id, target_user_id, relation_type, status='pending')`
- 解除关系按 `command_id` 幂等;同一关系同一时间只能有一条 pending 解除占位。
## 10. 相关IM
## 11. 相关IM
| IM | 通道 | 发送时机 |
| --- | --- | --- |
| `cp_application_created` | C2C | A 送 CP 礼物触发申请后A 发给 B |
| `cp_application_created` | 房间群 | A 送 CP 礼物触发申请后发到来源房间payload 带 `recipient_user_id=B` |
| `cp_application_accepted` | C2C | B 同意后B 发给 A |
| `cp_application_accepted` | C2C | B 同意后B 发给 Apayload 带 `c2c_notice_role=accepted_to_requester``message_text=我同意了你` |
| `cp_application_accepted` | C2C | B 同意后A 发给 Bpayload 带 `c2c_notice_role=accepted_to_acceptor``message_text=对方已同意你的申请` |
| `cp_application_accepted` | 房间群 | B 同意后发到来源房间payload 带 `recipient_user_id=A` |
| `cp_application_rejected` | C2C | B 拒绝后B 发给 A |
| `cp_application_rejected` | 房间群 | B 拒绝后发到来源房间payload 带 `recipient_user_id=A` |
| `cp_relationship_created` | 区域群 | 关系建立后发送区域播报 |
| 无 | 无 | 解除关系成功不发 IMApp 根据解除接口返回刷新 |
C2C 使用腾讯云 IM `From_Account` 表达真实操作人:申请为 A处理结果为 B。区域 IM 群 ID 使用 `hy_<app_code>_bc_r_<region_id>`,本地测试可通过 `group_id_prefix` 加前缀。
C2C 使用腾讯云 IM `From_Account` 表达会话方向:申请为 A拒绝为 B同意会发 `B -> A``A -> B` 两条。区域 IM 群 ID 使用 `hy_<app_code>_bc_r_<region_id>`,本地测试可通过 `group_id_prefix` 加前缀。
## 11. 错误码
## 12. 错误码
| 场景 | code | 说明 |
| --- | --- | --- |
@ -220,9 +282,11 @@ C2C 使用腾讯云 IM `From_Account` 表达真实操作人:申请为 A
| 申请已过期 | `CONFLICT` | 不能再同意;内部 gRPC 语义是 FailedPrecondition |
| 已有关系 | `CONFLICT` | A/B 任一方已经有 active 关系 |
| 无权限处理 | `PERMISSION_DENIED` | 不是申请目标用户 |
| 余额不足 | `INSUFFICIENT_BALANCE` | 解除关系扣金币失败 |
| 关系不可解除 | `CONFLICT` | 关系不是 active或已有 pending 解除 |
| 幂等冲突 | `CONFLICT` | command_id 或事件消费 payload 不一致 |
## 12. 验证项
## 13. 验证项
- CP 礼物发送成功后,`RoomGiftSent``gift_type_code=cp``cp_relation_type`、礼物 icon 快照。
- A 给 B 连发 `cp``brother``sister` 三种礼物,会生成三条 pending 申请。
@ -230,4 +294,5 @@ C2C 使用腾讯云 IM `From_Account` 表达真实操作人:申请为 A
- A/B 建立关系后A 或 B 都不能再和第三人建立 active 关系。
- pending 申请超过默认 24 小时后不能接受。
- active 关系双方互送任意礼物,亲密值按 `heat_value` 增加。
- active 关系解除会扣除后台配置的金币费用,扣费失败不改变关系状态。
- 同意、拒绝、关系成立的 IM 都写入 `notice_delivery_events`,失败可重试。

View File

@ -1,6 +1,6 @@
# CP关系Flutter对接
本文只写 App 需要对接的地址、参数、返回值和相关 IM。CP 关系包含 `cp``brother``sister` 三种类型;每个用户同时只能有一个 active 关系,不需要佩戴接口,也没有后台数量配置
本文只写 App 需要对接的地址、参数、返回值和相关 IM。CP 关系包含 `cp``brother``sister` 三种类型;CP 关系每个用户最多 1 个,兄弟和姐妹数量由后台 CP 配置控制;不需要佩戴接口
## 1. 礼物面板CP Tab
@ -274,7 +274,14 @@ X-App-Code: lalu
},
"intimacy_value": 1888,
"level": 2,
"current_level_threshold": 1000,
"next_level_threshold": 3000,
"needed_for_next_level": 1112,
"level_progress_percent": 44,
"max_level": false,
"breakup_cost_coins": 88,
"formed_at_ms": 1779259100000,
"ended_at_ms": 0,
"updated_at_ms": 1779259200000
}
],
@ -284,14 +291,66 @@ X-App-Code: lalu
说明:
- 每个用户同时只能有一个 active 关系。
- CP 关系每个用户最多 1 个;兄弟和姐妹可拥有数量以后台配置为准。
- A/B 两个用户之间同一时间只能存在一种 active 关系,不能同时是 CP、兄弟或姐妹。
- 不需要佩戴接口,资料卡直接展示当前 active 关系。
- 资料卡弹窗只使用后端返回的 `intimacy_value``level``current_level_threshold``next_level_threshold``needed_for_next_level``level_progress_percent``max_level`
- `max_level=true``next_level_threshold=0` 时表示没有下一等级,隐藏“距离下一级”和进度条。
- Flutter 不要用本地常量、礼物价格或后台配置接口自行推算进度条和“距离下一级”。
- `breakup_cost_coins` 是解除当前关系需要支付的金币数,弹窗和二次确认直接使用该值。
## 7. 相关IM
## 7. 解除关系
地址:
```http
POST /api/v1/cp/relationships/{relationship_id}/break
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json
{
"command_id": "break_cp_rel_10001_10002_1779259300000"
}
```
参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `relationship_id` | string | 是 | URL path关系 ID |
| `command_id` | string | 是 | 解除关系幂等键,同一次重试必须复用 |
返回值:
```json
{
"relationship": {
"relationship_id": "cp_rel_10001_10002",
"relation_type": "cp",
"status": "ended",
"breakup_cost_coins": 88,
"formed_at_ms": 1779259100000,
"ended_at_ms": 1779259300000,
"updated_at_ms": 1779259300000
},
"breakup_cost_coins": 88,
"wallet_transaction_id": "wallet_tx_break_1",
"coin_balance_after": 912
}
```
说明:
- 解除前先展示 `/api/v1/cp/relationships` 返回的 `breakup_cost_coins`
- 金币不足时接口返回 `INSUFFICIENT_BALANCE`,关系仍保持 active。
- 解除成功不发 C2C、房间 IM 或区域 IMFlutter 使用接口返回刷新资料卡和关系列表。
## 8. 相关IM
所有 IM 都是 Tencent `TIMCustomElem``MsgContent.Desc` 等于下表事件名,`MsgContent.Data` 是 JSON 字符串。客户端按 `event_id` 去重。
### 7.1 收到关系申请
### 8.1 收到关系申请
房间 IM 和 C2C IM 都会收到:
@ -345,9 +404,9 @@ Data
- C2C IM 在私聊里显示同意/拒绝按钮。
- `expires_at_ms <= server_time_ms` 时按钮置灰并刷新申请列表。
### 7.2 申请被同意
### 8.2 申请被同意
C2C 和房间 IM 都会收到
C2C 会收到双方互发的两条消息,房间 IM 仍然只给申请发起人 A 展示
| 字段 | 值 |
| --- | --- |
@ -362,13 +421,20 @@ Data 字段和申请消息一致,差异是:
| `decided_at_ms` | 处理时间 |
| `recipient_user_id` | 房间 IM 中为 A 的 user_id |
C2C 差异:
| 方向 | `c2c_notice_role` | `message_text` |
| --- | --- | --- |
| B -> A | `accepted_to_requester` | `我同意了你` |
| A -> B | `accepted_to_acceptor` | `对方已同意你的申请` |
处理:
- A 收到后刷新 `/api/v1/cp/relationships`
- B 操作成功后也刷新关系列表。
- 同一对用户其他 pending 申请需要置灰。
### 7.3 申请被拒绝
### 8.3 申请被拒绝
C2C 和房间 IM 都会收到:
@ -386,7 +452,7 @@ Data 字段和申请消息一致,差异是:
| `decided_at_ms` | 处理时间 |
| `recipient_user_id` | 房间 IM 中为 A 的 user_id |
### 7.4 区域关系成立播报
### 8.4 区域关系成立播报
用户组成任意关系后,会发送区域 IM
@ -453,3 +519,4 @@ Data
- 同一个 `application_id` 在房间和 C2C 里可能都出现,按钮状态要共享。
- 申请过期时间以 `expires_at_ms` 为准。
- 亲密值和等级以后端关系接口返回为准,不在 Flutter 里按礼物价格推算。
- 进度条和距离下一级只使用关系接口返回的阈值字段;字段缺失时不展示,不要伪造 `next_level_threshold``needed_for_next_level` 或进度百分比。

View File

@ -34,6 +34,18 @@
| `levels[].in_room_rewards` | 在房奖励候选 |
| `levels[].top1_rewards` | 贡献第一奖励候选 |
| `levels[].igniter_rewards` | 点火人奖励候选 |
| `levels[].*_rewards[].resource_group_id` | 奖励资源组 ID |
| `levels[].*_rewards[].resource_group` | 奖励资源组详情 |
| `levels[].*_rewards[].resource_group.items` | 资源组明细 |
| `levels[].*_rewards[].resource_group.items[].item_type` | `wallet_asset``resource` |
| `levels[].*_rewards[].resource_group.items[].wallet_asset_type` | 金币等奖励资产类型,例如 `COIN` |
| `levels[].*_rewards[].resource_group.items[].wallet_asset_amount` | 金币等奖励数量 |
| `levels[].*_rewards[].resource_group.items[].resource` | 聊天气泡、麦位特效等资源素材 |
| `levels[].*_rewards[].resource_group.items[].resource.resource_type` | 资源类型,例如 `chat_bubble``mic_effect` |
| `levels[].*_rewards[].resource_group.items[].resource.asset_url` | 资源主素材 |
| `levels[].*_rewards[].resource_group.items[].resource.preview_url` | 资源预览图 |
| `levels[].*_rewards[].resource_group.items[].resource.animation_url` | 资源动效 |
| `levels[].*_rewards[].resource_group.items[].duration_ms` | 资源有效期毫秒,`0` 表示永久 |
| `state.current_level` | 当前正在累计的等级 |
| `state.current_fuel` | 当前等级已累计燃料 |
| `state.fuel_threshold` | 当前等级阈值 |
@ -48,8 +60,12 @@
| `state.pending_launches[].igniter_user_id` | 点火人 |
| `state.pending_launches[].cover_url` | 小封面图 |
| `state.last_rewards` | 最近一次发射奖励结果 |
| `state.last_rewards[].resource_group_id` | 本次中奖资源组 ID |
| `state.last_rewards[].resource_group` | 本次中奖资源组详情,字段同上 |
| `server_time_ms` | 服务端 UTC 毫秒 |
`levels[].*_rewards` 表示 `in_room_rewards``top1_rewards``igniter_rewards` 三个奖励池。客户端展示金币数量、奖励天数、聊天气泡、麦位特效时,直接读 `resource_group.items`
## 送礼
地址:`POST /api/v1/rooms/gift/send`
@ -258,6 +274,8 @@
| `rewards[].grant_id` | 发奖回执 |
| `rewards[].status` | 发奖状态 |
奖励 IM 用于通知本次哪些人中奖。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`
## 客户端处理
- 进房先调 `GET /api/v1/rooms/{room_id}/rocket`

View File

@ -1824,7 +1824,7 @@ paths:
- manager-center
summary: 经理中心赠送单个资源
operationId: grantManagerResource
description: 客户端只提交命令 ID、目标用户和资源 ID数量固定为 1有效期固定为 0`grant_source` 固定为 `manager_center`。
description: 客户端提交命令 ID、目标用户、资源 ID 和可选有效期;数量固定为 1有效期只能是 7/30/90 天,未传默认 7 天`grant_source` 固定为 `manager_center`。
security:
- BearerAuth: []
parameters:
@ -3575,10 +3575,16 @@ definitions:
resource_id:
type: string
description: 资源 IDwallet-service 会在事务内复验 `manager_grant_enabled`。
duration_ms:
type: integer
format: int64
enum: [604800000, 2592000000, 7776000000]
default: 604800000
description: 资源有效期,仅允许 7/30/90 天;未传时 gateway 默认 7 天。
reason:
type: string
default: manager_center
description: 不接受 `quantity`、`duration_ms`、`wallet_asset_amount` 等客户端计算字段。
description: 不接受 `quantity`、`wallet_asset_amount` 等客户端计算字段。
ManagerResourceGrantData:
type: object
properties:

View File

@ -3,7 +3,7 @@
## 服务边界
- `room-service` 是火箭进度、点火、待发射队列和奖励结算 owner。
- `gateway-service` 只提供 App HTTP 入口和鉴权后的协议转换。
- `gateway-service` 只提供 App HTTP 入口和鉴权后的协议转换`GET /api/v1/rooms/{room_id}/rocket` 会按奖励 `resource_group_id` 查询 wallet-service 并嵌入 `resource_group.items`
- `server/admin` 只读写 `room-service` 内部配置接口,不直连 room 数据库。
- `wallet-service` 负责资源组发放。
- `activity-service` 负责区域/全局飘屏和带用户资料的奖励房间 IM。
@ -92,6 +92,7 @@
| `RoomRocketRewardGranted` | `room_rocket_reward_granted` 房间奖励 IM | activity-service |
奖励 IM 由 activity-service 查询用户公开资料后组装,包含头像、昵称、短 ID 和奖励信息。room-service 不反查 user-service。
奖励资源组内容不写入 Room Cell客户端需要金币数量、有效期、聊天气泡或麦位特效素材时通过 rocket GET 接口读取展开后的 `resource_group`
## 高频处理

View File

@ -86,6 +86,29 @@ func RegionBroadcastGroupIDWithPrefix(prefix string, value string, regionID int6
return groupID, nil
}
// RoomGroupID 生成腾讯云 IM 房间群 ID。
func RoomGroupID(value string) (string, error) {
return RoomGroupIDWithPrefix("", value)
}
// RoomGroupIDWithPrefix 生成带环境前缀的腾讯云 IM 房间群 ID。
func RoomGroupIDWithPrefix(prefix string, value string) (string, error) {
prefix = strings.TrimSpace(prefix)
if err := validateGroupIDPrefix(prefix); err != nil {
return "", err
}
room := strings.TrimSpace(value)
if !roomid.ValidStringID(room) {
return "", fmt.Errorf("room_id is invalid for group_id")
}
// 房间群 GroupID 必须和内部 room_id 保持一致;线上已有腾讯 IM 群也是这个 ID不能在投递边界改写。
groupID := prefix + room
if len(groupID) > roomid.MaxStringIDLength {
return "", fmt.Errorf("room group_id is too long")
}
return groupID, nil
}
// Parse 在调用任何 room guard 前完成 GroupID 分类。
// 解析顺序必须先播报群、后房间群;否则 hy_<app>_bc_* 这类合法短字符串可能被 roomid 规则吞掉。
func Parse(groupID string) ParsedGroup {
@ -101,8 +124,8 @@ func Parse(groupID string) ParsedGroup {
return ParsedGroup{Kind: KindUnknown}
}
if roomid.ValidStringID(groupID) {
return ParsedGroup{Kind: KindRoom, RoomID: groupID}
if roomID, ok := RoomIDFromRoomGroupID(groupID); ok {
return ParsedGroup{Kind: KindRoom, RoomID: roomID}
}
return ParsedGroup{Kind: KindUnknown}
}
@ -118,17 +141,32 @@ func ParseWithPrefix(groupID string, prefix string) ParsedGroup {
return Parse(groupID)
}
if after, ok := strings.CutPrefix(groupID, prefix); ok {
return parseBroadcastGroupID(after)
if parsed := parseBroadcastGroupID(after); parsed.Kind != KindUnknown {
return parsed
}
if roomID, ok := RoomIDFromRoomGroupID(after); ok {
return ParsedGroup{Kind: KindRoom, RoomID: roomID}
}
return ParsedGroup{Kind: KindUnknown}
}
if strings.HasPrefix(groupID, "hy_") && strings.Contains(groupID, "_bc_") {
return ParsedGroup{Kind: KindUnknown}
}
if roomid.ValidStringID(groupID) {
return ParsedGroup{Kind: KindRoom, RoomID: groupID}
if roomID, ok := RoomIDFromRoomGroupID(groupID); ok {
return ParsedGroup{Kind: KindRoom, RoomID: roomID}
}
return ParsedGroup{Kind: KindUnknown}
}
// RoomIDFromRoomGroupID 把腾讯云 IM 房间群 ID 还原成内部 room_id。
func RoomIDFromRoomGroupID(groupID string) (string, bool) {
groupID = strings.TrimSpace(groupID)
if !roomid.ValidStringID(groupID) {
return "", false
}
return groupID, true
}
func parseBroadcastGroupID(groupID string) ParsedGroup {
if strings.HasPrefix(groupID, "hy_") && strings.HasSuffix(groupID, globalSuffix) {
app := strings.TrimSuffix(strings.TrimPrefix(groupID, "hy_"), globalSuffix)

View File

@ -43,6 +43,21 @@ func TestParseRoomAndRejectInvalidBroadcast(t *testing.T) {
}
}
func TestRoomGroupIDPreservesRoomID(t *testing.T) {
roomID := "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca"
groupID, err := RoomGroupID(roomID)
if err != nil {
t.Fatalf("RoomGroupID failed: %v", err)
}
if groupID != roomID {
t.Fatalf("room group mismatch: %s", groupID)
}
parsed := Parse(groupID)
if parsed.Kind != KindRoom || parsed.RoomID != roomID {
t.Fatalf("room group parse mismatch: %+v", parsed)
}
}
func TestBroadcastGroupIDWithPrefix(t *testing.T) {
global, err := GlobalBroadcastGroupIDWithPrefix("test_", "lalu")
if err != nil {
@ -73,4 +88,14 @@ func TestBroadcastGroupIDWithPrefix(t *testing.T) {
if parsed := ParseWithPrefix("room-1001", "test_"); parsed.Kind != KindRoom || parsed.RoomID != "room-1001" {
t.Fatalf("room parse with prefix mismatch: %+v", parsed)
}
prefixedRoom, err := RoomGroupIDWithPrefix("test_", "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca")
if err != nil {
t.Fatalf("RoomGroupIDWithPrefix failed: %v", err)
}
if prefixedRoom != "test_lalu_2963d975-316e-444d-ad5c-f82fc36c28ca" {
t.Fatalf("prefixed room group mismatch: %s", prefixedRoom)
}
if parsed := ParseWithPrefix(prefixedRoom, "test_"); parsed.Kind != KindRoom || parsed.RoomID != "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca" {
t.Fatalf("prefixed room parse mismatch: %+v", parsed)
}
}

View File

@ -6,6 +6,7 @@ import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@ -94,6 +95,7 @@ type AccountProfile struct {
// RoomEvent 是 room-service 发给腾讯 IM 的房间系统消息负载。
type RoomEvent struct {
GroupID string `json:"-"`
EventID string `json:"event_id"`
RoomID string `json:"room_id"`
EventType string `json:"event_type"`
@ -220,6 +222,10 @@ func (c *RESTClient) PublishRoomEvent(ctx context.Context, event RoomEvent) erro
// event_id 是客户端去重、日志追踪和补偿投递判断的共同锚点。
return fmt.Errorf("room event is incomplete")
}
groupID := strings.TrimSpace(event.GroupID)
if groupID == "" {
groupID = event.RoomID
}
eventPayload, err := json.Marshal(event)
if err != nil {
@ -227,7 +233,7 @@ func (c *RESTClient) PublishRoomEvent(ctx context.Context, event RoomEvent) erro
}
return c.PublishGroupCustomMessage(ctx, CustomGroupMessage{
GroupID: event.RoomID,
GroupID: groupID,
EventID: event.EventID,
Desc: event.EventType,
Ext: "room_system_message",
@ -473,6 +479,31 @@ type restResponse struct {
ErrorCode int `json:"ErrorCode"`
}
// RESTError 保留腾讯云 IM 业务错误码,让上层能区分缺群、参数错误和限流。
type RESTError struct {
Code int
Info string
}
// Error 实现 error沿用既有日志格式避免线上检索规则失效。
func (e RESTError) Error() string {
return fmt.Sprintf("tencent im rest failed: code=%d info=%s", e.Code, e.Info)
}
// IsRESTErrorCode 判断错误链中是否包含指定腾讯云 IM 业务错误码。
func IsRESTErrorCode(err error, codes ...int) bool {
var restErr RESTError
if !errors.As(err, &restErr) {
return false
}
for _, code := range codes {
if restErr.Code == code {
return true
}
}
return false
}
func (r restResponse) err() error {
if r.ErrorCode == 0 && strings.EqualFold(r.ActionStatus, "OK") {
return nil
@ -482,7 +513,7 @@ func (r restResponse) err() error {
return nil
}
return fmt.Errorf("tencent im rest failed: code=%d info=%s", r.ErrorCode, r.ErrorInfo)
return RESTError{Code: r.ErrorCode, Info: r.ErrorInfo}
}
func isAccountAlreadyImported(response restResponse) bool {

View File

@ -58,6 +58,7 @@ func TestRESTClientPublishRoomEventBuildsCustomMessage(t *testing.T) {
})
err := client.PublishRoomEvent(context.Background(), RoomEvent{
GroupID: "room_1001",
EventID: "evt-1",
RoomID: "room-1001",
EventType: "room_gift_sent",
@ -73,7 +74,7 @@ func TestRESTClientPublishRoomEventBuildsCustomMessage(t *testing.T) {
if capturedPath != "/"+sendGroupMsgCommand {
t.Fatalf("path mismatch: got %q", capturedPath)
}
if capturedBody["GroupId"] != "room-1001" {
if capturedBody["GroupId"] != "room_1001" {
t.Fatalf("group id mismatch: %+v", capturedBody)
}
body, ok := capturedBody["MsgBody"].([]any)
@ -91,6 +92,9 @@ func TestRESTClientPublishRoomEventBuildsCustomMessage(t *testing.T) {
if !strings.Contains(content["Data"].(string), `"event_id":"evt-1"`) {
t.Fatalf("event payload missing event_id: %s", content["Data"])
}
if !strings.Contains(content["Data"].(string), `"room_id":"room-1001"`) {
t.Fatalf("event payload must keep internal room_id: %s", content["Data"])
}
}
func TestRESTClientPublishGroupCustomMessageBuildsBroadcastPayload(t *testing.T) {

View File

@ -561,6 +561,7 @@ func roomPinFromProto(input *roomv1.AdminRoomPin) RoomPin {
func roomRocketConfigToProto(input RoomRocketConfig) *roomv1.AdminRoomRocketConfig {
return &roomv1.AdminRoomRocketConfig{
AppCode: input.AppCode,
Enabled: input.Enabled,
FuelSource: input.FuelSource,
LaunchDelayMs: input.LaunchDelayMS,

View File

@ -0,0 +1,10 @@
package roomclient
import "testing"
func TestRoomRocketConfigToProtoKeepsAppCode(t *testing.T) {
config := roomRocketConfigToProto(RoomRocketConfig{AppCode: "lalu"})
if config.GetAppCode() != "lalu" {
t.Fatalf("proto app code mismatch: %q", config.GetAppCode())
}
}

View File

@ -52,6 +52,7 @@ type relationDTO struct {
DisplayName string `json:"displayName"`
MaxCountPerUser int32 `json:"maxCountPerUser"`
ApplicationExpireHours int32 `json:"applicationExpireHours"`
BreakupCostCoins int64 `json:"breakupCostCoins"`
Status string `json:"status"`
Levels []levelDTO `json:"levels"`
UpdatedAtMS int64 `json:"updatedAtMs"`
@ -109,15 +110,16 @@ func (s *Service) UpdateConfig(ctx context.Context, appCode string, req updateRe
for _, relation := range relations {
if _, err := tx.ExecContext(ctx, `
INSERT INTO user_cp_relation_configs (
app_code, relation_type, max_count_per_user, application_expire_hours,
app_code, relation_type, max_count_per_user, application_expire_hours, breakup_cost_coins,
status, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
max_count_per_user = VALUES(max_count_per_user),
application_expire_hours = VALUES(application_expire_hours),
breakup_cost_coins = VALUES(breakup_cost_coins),
status = VALUES(status),
updated_at_ms = VALUES(updated_at_ms)`,
appCode, relation.RelationType, relation.MaxCountPerUser, relation.ApplicationExpireHours,
appCode, relation.RelationType, relation.MaxCountPerUser, relation.ApplicationExpireHours, relation.BreakupCostCoins,
relation.Status, nowMs, nowMs,
); err != nil {
return configDTO{}, err
@ -149,7 +151,7 @@ func (s *Service) UpdateConfig(ctx context.Context, appCode string, req updateRe
func (s *Service) listRelations(ctx context.Context, appCode string) ([]relationDTO, error) {
rows, err := s.db.QueryContext(ctx, `
SELECT relation_type, max_count_per_user, application_expire_hours, status, updated_at_ms
SELECT relation_type, max_count_per_user, application_expire_hours, breakup_cost_coins, status, updated_at_ms
FROM user_cp_relation_configs
WHERE app_code = ?`, appCode)
if err != nil {
@ -159,7 +161,7 @@ func (s *Service) listRelations(ctx context.Context, appCode string) ([]relation
relationsByType := map[string]relationDTO{}
for rows.Next() {
var item relationDTO
if err := rows.Scan(&item.RelationType, &item.MaxCountPerUser, &item.ApplicationExpireHours, &item.Status, &item.UpdatedAtMS); err != nil {
if err := rows.Scan(&item.RelationType, &item.MaxCountPerUser, &item.ApplicationExpireHours, &item.BreakupCostCoins, &item.Status, &item.UpdatedAtMS); err != nil {
return nil, err
}
item.DisplayName = relationLabels[item.RelationType]
@ -222,9 +224,9 @@ func (s *Service) ensureDefaultConfig(ctx context.Context, appCode string, nowMs
}
if _, err := s.db.ExecContext(ctx, `
INSERT INTO user_cp_relation_configs (
app_code, relation_type, max_count_per_user, application_expire_hours,
app_code, relation_type, max_count_per_user, application_expire_hours, breakup_cost_coins,
status, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, 'active', ?, ?)
) VALUES (?, ?, ?, ?, 0, 'active', ?, ?)
ON DUPLICATE KEY UPDATE relation_type = VALUES(relation_type)`,
appCode, relationType, maxCount, defaultExpireHours, nowMs, nowMs,
); err != nil {
@ -256,6 +258,7 @@ func (s *Service) ensureSchema(ctx context.Context) error {
relation_type VARCHAR(32) NOT NULL,
max_count_per_user INT NOT NULL,
application_expire_hours INT NOT NULL,
breakup_cost_coins BIGINT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL DEFAULT 'active',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
@ -263,6 +266,11 @@ func (s *Service) ensureSchema(ctx context.Context) error {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`); err != nil {
return err
}
if err := s.ensureColumn(ctx, "user_cp_relation_configs", "breakup_cost_coins", `
ALTER TABLE user_cp_relation_configs
ADD COLUMN breakup_cost_coins BIGINT NOT NULL DEFAULT 0 AFTER application_expire_hours`); err != nil {
return err
}
_, err := s.db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS user_cp_level_rules (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
@ -280,6 +288,23 @@ func (s *Service) ensureSchema(ctx context.Context) error {
return err
}
func (s *Service) ensureColumn(ctx context.Context, tableName string, columnName string, alterSQL string) error {
var found string
err := s.db.QueryRowContext(ctx, `
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?
LIMIT 1`, tableName, columnName).Scan(&found)
if err == nil {
return nil
}
if !errors.Is(err, sql.ErrNoRows) {
return err
}
_, err = s.db.ExecContext(ctx, alterSQL)
return err
}
func normalizeRelations(input []relationDTO) ([]relationDTO, error) {
if len(input) == 0 {
return nil, fmt.Errorf("%w: 关系配置不能为空", ErrInvalidArgument)
@ -305,6 +330,9 @@ func normalizeRelations(input []relationDTO) ([]relationDTO, error) {
if relation.ApplicationExpireHours <= 0 {
relation.ApplicationExpireHours = defaultExpireHours
}
if relation.BreakupCostCoins < 0 {
return nil, fmt.Errorf("%w: %s解除费用不能小于0", ErrInvalidArgument, relationLabels[relation.RelationType])
}
relation.Status = defaultString(strings.TrimSpace(relation.Status), statusActive)
relation.Levels = normalizeLevelDefaults(relation.Levels)
if err := validateLevels(relation.Levels); err != nil {

View File

@ -71,6 +71,7 @@ type giftDTO struct {
PresentationJSON string `json:"presentationJson"`
PriceVersion string `json:"priceVersion"`
GiftTypeCode string `json:"giftTypeCode"`
CPRelationType string `json:"cpRelationType"`
ChargeAssetType string `json:"chargeAssetType"`
CoinPrice int64 `json:"coinPrice"`
EffectiveFromMS int64 `json:"effectiveFromMs"`
@ -293,6 +294,7 @@ func giftFromProto(gift *walletv1.GiftConfig) giftDTO {
PresentationJSON: gift.GetPresentationJson(),
PriceVersion: gift.GetPriceVersion(),
GiftTypeCode: gift.GetGiftTypeCode(),
CPRelationType: gift.GetCpRelationType(),
ChargeAssetType: gift.GetChargeAssetType(),
CoinPrice: gift.GetCoinPrice(),
EffectiveFromMS: gift.GetEffectiveFromMs(),

View File

@ -131,6 +131,7 @@ type giftRequest struct {
PresentationJSON string `json:"presentationJson"`
PriceVersion string `json:"priceVersion"`
GiftTypeCode string `json:"giftTypeCode"`
CPRelationType string `json:"cpRelationType"`
ChargeAssetType string `json:"chargeAssetType"`
CoinPrice int64 `json:"coinPrice"`
HeatValue int64 `json:"heatValue"` // 兼容旧后台请求;新后台不再展示或提交热度。
@ -366,6 +367,7 @@ func (r giftRequest) createProto(c *gin.Context) *walletv1.CreateGiftConfigReque
PresentationJson: strings.TrimSpace(r.PresentationJSON),
PriceVersion: strings.TrimSpace(r.PriceVersion),
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
CpRelationType: r.cpRelationType(),
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
CoinPrice: r.CoinPrice,
GiftPointAmount: 0,
@ -391,6 +393,7 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update
PresentationJson: strings.TrimSpace(r.PresentationJSON),
PriceVersion: strings.TrimSpace(r.PriceVersion),
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
CpRelationType: r.cpRelationType(),
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
CoinPrice: r.CoinPrice,
GiftPointAmount: 0,
@ -404,6 +407,27 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update
}
}
func (r giftRequest) cpRelationType() string {
if strings.TrimSpace(r.GiftTypeCode) != "cp" {
return ""
}
// CP 关系类型最终由 wallet-service 写回 presentation_json兼容旧前端只提交 JSON 的请求,避免保存时把兄弟/姐妹关系清掉。
if value := strings.TrimSpace(r.CPRelationType); value != "" {
return value
}
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(r.PresentationJSON)), &payload); err != nil {
return ""
}
if value, ok := payload["cp_relation_type"].(string); ok {
return strings.TrimSpace(value)
}
if value, ok := payload["cpRelationType"].(string); ok {
return strings.TrimSpace(value)
}
return ""
}
func (r giftTypeRequest) upsertProto(c *gin.Context, typeCode string) *walletv1.UpsertGiftTypeConfigRequest {
return &walletv1.UpsertGiftTypeConfigRequest{
RequestId: middleware.CurrentRequestID(c),

View File

@ -0,0 +1,63 @@
package resource
import (
"net/http/httptest"
"testing"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/middleware"
"github.com/gin-gonic/gin"
)
func TestGiftRequestProtoKeepsExplicitCPRelationType(t *testing.T) {
req := giftRequest{
GiftTypeCode: "cp",
CPRelationType: "brother",
PresentationJSON: `{"cp_relation_type":"sister"}`,
}
proto := req.updateProto(newGiftRequestTestContext(), "love")
if proto.GetCpRelationType() != "brother" {
t.Fatalf("explicit cp relation type should win, got %q", proto.GetCpRelationType())
}
}
func TestGiftRequestProtoFallsBackToPresentationCPRelationType(t *testing.T) {
req := giftRequest{
GiftID: "love",
GiftTypeCode: "cp",
PresentationJSON: `{"cp_relation_type":"sister"}`,
}
proto := req.createProto(newGiftRequestTestContext())
if proto.GetCpRelationType() != "sister" {
t.Fatalf("presentation cp relation type should be forwarded, got %q", proto.GetCpRelationType())
}
}
func TestGiftRequestProtoClearsCPRelationTypeForNonCPGift(t *testing.T) {
req := giftRequest{
GiftTypeCode: "normal",
CPRelationType: "brother",
PresentationJSON: `{"cp_relation_type":"brother"}`,
}
proto := req.updateProto(newGiftRequestTestContext(), "rose")
if proto.GetCpRelationType() != "" {
t.Fatalf("non-cp gift should not forward cp relation type, got %q", proto.GetCpRelationType())
}
}
func newGiftRequestTestContext() *gin.Context {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
request := httptest.NewRequest("POST", "/admin/gifts", nil)
c.Request = request.WithContext(appctx.WithContext(request.Context(), "lalu"))
c.Set(middleware.ContextRequestID, "resource-test-request")
return c
}

View File

@ -7,6 +7,7 @@ import (
"slices"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/roomclient"
)
@ -108,17 +109,7 @@ func (s *Service) UpdateConfig(ctx context.Context, req updateRoomRocketConfigRe
if s.roomClient == nil {
return RoomRocketConfig{}, fmt.Errorf("room service client is not configured")
}
config, err := normalizeConfig(RoomRocketConfig{
Enabled: req.Enabled,
FuelSource: req.FuelSource,
LaunchDelayMS: req.LaunchDelayMS,
BroadcastEnabled: req.BroadcastEnabled,
BroadcastScope: req.BroadcastScope,
BroadcastDelayMS: req.BroadcastDelayMS,
RewardStackPolicy: req.RewardStackPolicy,
Levels: req.Levels,
GiftFuelRules: req.GiftFuelRules,
})
config, err := normalizeConfig(configFromUpdateRequest(ctx, req))
if err != nil {
return RoomRocketConfig{}, err
}
@ -132,6 +123,22 @@ func (s *Service) UpdateConfig(ctx context.Context, req updateRoomRocketConfigRe
return configFromClient(saved)
}
func configFromUpdateRequest(ctx context.Context, req updateRoomRocketConfigRequest) RoomRocketConfig {
// 房间火箭配置按 App 隔离;更新请求体不让前端传 app_code必须使用中间件写入的上下文值。
return RoomRocketConfig{
AppCode: appctx.FromContext(ctx),
Enabled: req.Enabled,
FuelSource: req.FuelSource,
LaunchDelayMS: req.LaunchDelayMS,
BroadcastEnabled: req.BroadcastEnabled,
BroadcastScope: req.BroadcastScope,
BroadcastDelayMS: req.BroadcastDelayMS,
RewardStackPolicy: req.RewardStackPolicy,
Levels: req.Levels,
GiftFuelRules: req.GiftFuelRules,
}
}
func defaultConfig(appCode string) RoomRocketConfig {
levels := make([]RoomRocketLevelConfig, 0, roomRocketLevelCount)
for index, threshold := range defaultLevelFuelThresholds {

View File

@ -1,10 +1,24 @@
package roomrocket
import (
"context"
"errors"
"testing"
"hyapp-admin-server/internal/appctx"
)
func TestConfigFromUpdateRequestUsesContextAppCode(t *testing.T) {
request := updateRoomRocketConfigRequest{Levels: defaultConfig("ignored").Levels}
config := configFromUpdateRequest(appctx.WithContext(context.Background(), "LALU"), request)
if config.AppCode != "lalu" {
t.Fatalf("config app code should come from context: %+v", config)
}
if _, err := normalizeConfig(config); err != nil {
t.Fatalf("normalizeConfig should accept context app code: %v", err)
}
}
func TestNormalizeConfigDefaultsAndSortsLevels(t *testing.T) {
config, err := normalizeConfig(RoomRocketConfig{
AppCode: "lalu",

View File

@ -58,7 +58,7 @@ rocketmq:
secret_key: ""
namespace: ""
room_outbox:
enabled: false
enabled: true
topic: "hyapp_room_outbox"
consumer_group: "hyapp-activity-room-outbox"
consumer_max_reconsume_times: 16

View File

@ -195,6 +195,29 @@ type EventResult struct {
RewardJobCount int64
}
// SetUserLevelCommand 描述经理中心直接设置用户等级的事实输入。
// TotalValue 不由调用方提交activity-service 会读取目标等级阈值后在事务内写入。
type SetUserLevelCommand struct {
CommandID string
UserID int64
Track string
Level int32
OperatorUserID int64
Reason string
}
// SetUserLevelResult 返回经理设置等级后的账户事实和补发奖励数量。
type SetUserLevelResult struct {
EventID string
Status string
Track string
PreviousLevel int32
NewLevel int32
TotalValue int64
RewardJobCount int64
ServerTimeMS int64
}
// RewardJob 是等级奖励资源组的异步发放任务。
type RewardJob struct {
AppCode string

View File

@ -32,6 +32,7 @@ type Repository interface {
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)
SetUserLevel(ctx context.Context, command domain.SetUserLevelCommand, nowMS int64) (domain.SetUserLevelResult, error)
UpsertLevelTrack(ctx context.Context, command domain.TrackCommand, nowMS int64) (domain.Track, bool, error)
UpsertLevelRule(ctx context.Context, command domain.RuleCommand, nowMS int64) (domain.Rule, bool, error)
UpsertLevelTier(ctx context.Context, command domain.TierCommand, nowMS int64) (domain.Tier, bool, error)
@ -220,6 +221,30 @@ func (s *Service) ConsumeLevelEvent(ctx context.Context, event domain.ValueEvent
return s.repository.ConsumeLevelEvent(ctx, event, s.now().UnixMilli())
}
// SetUserLevel 是经理中心的直接等级调整入口。
// 它不接收累计值避免调用方伪造进度service 只校验轨道、等级和操作者,阈值读取与奖励补发在 repository 事务里完成。
func (s *Service) SetUserLevel(ctx context.Context, command domain.SetUserLevelCommand) (domain.SetUserLevelResult, error) {
if err := s.requireRepository(); err != nil {
return domain.SetUserLevelResult{}, err
}
command.CommandID = strings.TrimSpace(command.CommandID)
command.Track = normalizeTrack(command.Track)
command.Reason = strings.TrimSpace(command.Reason)
if command.CommandID == "" || command.UserID <= 0 || command.OperatorUserID <= 0 {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "set user level command is incomplete")
}
if !validTrack(command.Track) {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "track is invalid")
}
if command.Level < 1 || command.Level > 11 {
return domain.SetUserLevelResult{}, xerr.New(xerr.InvalidArgument, "level must be between 1 and 11")
}
if command.Reason == "" {
command.Reason = "manager_center_level"
}
return s.repository.SetUserLevel(ctx, command, s.now().UnixMilli())
}
// HandleRoomEvent 把 room-service 已提交送礼事实映射为财富和魅力两条等级增量。
func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) (int32, error) {
if envelope == nil {

View File

@ -265,6 +265,112 @@ func (r *Repository) ConsumeLevelEvent(ctx context.Context, event domain.ValueEv
}, nil
}
// SetUserLevel 在一个事务里把用户某条轨道直接设置到目标等级阈值。
// 经理中心需要支持降级,所以这里不能复用只接受正增量的 ConsumeLevelEvent但仍写 growth_level_value_events 保留幂等事件和审计来源。
func (r *Repository) SetUserLevel(ctx context.Context, command domain.SetUserLevelCommand, nowMS int64) (domain.SetUserLevelResult, error) {
if r == nil || r.db == nil {
return domain.SetUserLevelResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
eventID := "manager_level:" + command.CommandID
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.SetUserLevelResult{}, err
}
defer func() { _ = tx.Rollback() }()
account, err := r.ensureLevelAccountForUpdate(ctx, tx, command.UserID, command.Track, nowMS)
if err != nil {
return domain.SetUserLevelResult{}, err
}
rules, err := r.listActiveLevelRules(ctx, tx, command.Track)
if err != nil {
return domain.SetUserLevelResult{}, err
}
targetValue, ok := requiredValueForLevel(rules, command.Level)
if !ok {
return domain.SetUserLevelResult{}, xerr.New(xerr.NotFound, "level rule not found")
}
tiers, err := r.listActiveLevelTiers(ctx, tx, command.Track)
if err != nil {
return domain.SetUserLevelResult{}, err
}
delta := targetValue - account.TotalValue
inserted, err := r.insertLevelValueEvent(ctx, tx, domain.ValueEvent{
EventID: eventID,
SourceEventID: command.CommandID,
SourceService: "manager_center",
SourceEventType: "ManagerSetUserLevel",
UserID: command.UserID,
Track: command.Track,
MetricType: "manager_set_level",
ValueDelta: delta,
OccurredAtMS: nowMS,
DimensionsJSON: normalizeJSONMap(map[string]any{
"operator_user_id": command.OperatorUserID,
"target_level": command.Level,
"reason": command.Reason,
}),
}, nowMS)
if err != nil {
return domain.SetUserLevelResult{}, err
}
if !inserted {
if err := tx.Commit(); err != nil {
return domain.SetUserLevelResult{}, err
}
return domain.SetUserLevelResult{
EventID: eventID,
Status: domain.EventStatusDuplicate,
Track: command.Track,
NewLevel: account.CurrentLevel,
TotalValue: account.TotalValue,
ServerTimeMS: nowMS,
}, nil
}
previousLevel := account.CurrentLevel
previousTierID := account.CurrentTierID
nextLevel := levelForValue(rules, targetValue)
nextTier := tierForLevel(tiers, nextLevel)
nextTierID := nextTier.TierID
levelUpdatedAtMS := account.LevelUpdatedAtMS
if nextLevel != previousLevel {
levelUpdatedAtMS = nowMS
}
tierUpdatedAtMS := account.TierUpdatedAtMS
if nextTierID != previousTierID {
tierUpdatedAtMS = nowMS
}
if err := r.updateLevelAccount(ctx, tx, command.UserID, command.Track, targetValue, nextLevel, nextTierID, levelUpdatedAtMS, tierUpdatedAtMS, nowMS); err != nil {
return domain.SetUserLevelResult{}, err
}
if err := r.insertLevelHistory(ctx, tx, domain.ValueEvent{EventID: eventID, UserID: command.UserID, Track: command.Track}, previousLevel, nextLevel, previousTierID, nextTierID, targetValue, nowMS); err != nil {
return domain.SetUserLevelResult{}, err
}
// 经理直接设置等级时要补齐从 1 级到目标等级之间缺失的奖励;唯一键保证已发过的等级不会重复入队。
rewardJobCount, err := r.createLevelRewardJobs(ctx, tx, command.UserID, command.Track, 0, nextLevel, 0, nextTier, rules, nowMS)
if err != nil {
return domain.SetUserLevelResult{}, err
}
badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, nextLevel)
if err := r.upsertLevelDisplayProfile(ctx, tx, command.UserID, command.Track, nextLevel, nextTier, badgeResourceID, badgeSourceLevel, nowMS); err != nil {
return domain.SetUserLevelResult{}, err
}
if err := tx.Commit(); err != nil {
return domain.SetUserLevelResult{}, err
}
return domain.SetUserLevelResult{
EventID: eventID,
Status: domain.EventStatusConsumed,
Track: command.Track,
PreviousLevel: previousLevel,
NewLevel: nextLevel,
TotalValue: targetValue,
RewardJobCount: rewardJobCount,
ServerTimeMS: nowMS,
}, nil
}
func (r *Repository) UpsertLevelTrack(ctx context.Context, command domain.TrackCommand, nowMS int64) (domain.Track, bool, error) {
if r == nil || r.db == nil {
return domain.Track{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
@ -844,6 +950,15 @@ func levelForValue(rules []domain.Rule, totalValue int64) int32 {
return level
}
func requiredValueForLevel(rules []domain.Rule, level int32) (int64, bool) {
for _, rule := range rules {
if rule.Level == level && rule.Status == domain.StatusActive {
return rule.RequiredValue, true
}
}
return 0, false
}
func tierForLevel(tiers []domain.Tier, level int32) domain.Tier {
if level < 0 {
return domain.Tier{}
@ -856,6 +971,14 @@ func tierForLevel(tiers []domain.Tier, level int32) domain.Tier {
return domain.Tier{}
}
func normalizeJSONMap(value map[string]any) string {
data, err := json.Marshal(value)
if err != nil {
return "{}"
}
return string(data)
}
func tierByID(tiers []domain.Tier, tierID int64) domain.Tier {
if tierID <= 0 {
return domain.Tier{}

View File

@ -120,6 +120,31 @@ func (s *GrowthLevelServer) ConsumeLevelEvent(ctx context.Context, req *activity
}, nil
}
func (s *GrowthLevelServer) SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.SetUserLevel(ctx, domain.SetUserLevelCommand{
CommandID: req.GetCommandId(),
UserID: req.GetUserId(),
Track: req.GetTrack(),
Level: req.GetLevel(),
OperatorUserID: req.GetOperatorUserId(),
Reason: req.GetReason(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.SetUserLevelResponse{
EventId: result.EventID,
Status: result.Status,
Track: result.Track,
PreviousLevel: result.PreviousLevel,
NewLevel: result.NewLevel,
TotalValue: result.TotalValue,
RewardJobCount: result.RewardJobCount,
ServerTimeMs: result.ServerTimeMS,
}, 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())

View File

@ -116,6 +116,7 @@ func New(cfg config.Config) (*App, error) {
"login_ip_risk": userCron.ProcessLoginIPRiskBatch,
"user_region_rebuild": userCron.ProcessRegionRebuildBatch,
"mic_open_session_compensation": userCron.CompensateMicOpenSessions,
"manager_user_block_expiry": userCron.ExpireManagerUserBlocks,
"message_fanout": activityCron.ProcessMessageFanoutBatch,
"growth_level_reward": activityCron.ProcessLevelRewardBatch,
"achievement_reward": activityCron.ProcessAchievementRewardBatch,

View File

@ -222,6 +222,14 @@ func defaultTasks() map[string]TaskConfig {
PendingPublishMaxAge: "2m",
PublishingSessionMaxAge: "12h",
},
"manager_user_block_expiry": {
Enabled: true,
Interval: "30s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
}
}

View File

@ -38,6 +38,12 @@ func (c *UserCronClient) CompensateMicOpenSessions(ctx context.Context, req sche
return userCronResult(resp), err
}
// ExpireManagerUserBlocks handles timed manager-center bans after their unblock time.
func (c *UserCronClient) ExpireManagerUserBlocks(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) {
resp, err := c.client.ExpireManagerUserBlocks(ctx, userCronRequest(req))
return userCronResult(resp), err
}
func userCronRequest(req scheduler.BatchRequest) *userv1.CronBatchRequest {
return &userv1.CronBatchRequest{
Meta: &userv1.RequestMeta{

View File

@ -80,6 +80,7 @@ CREATE TABLE IF NOT EXISTS game_launch_sessions (
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY(app_code, session_id),
KEY idx_game_launch_user(app_code, user_id, created_at_ms),
KEY idx_game_launch_recent(app_code, user_id, scene, created_at_ms, game_id),
KEY idx_game_launch_token(app_code, launch_token_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏启动会话表';

View File

@ -0,0 +1,88 @@
package game
import (
"encoding/json"
"net/url"
"strings"
gamedomain "hyapp/services/game-service/internal/domain/game"
)
type bridgeScriptConfig struct {
URL string `json:"bridge_script_url"`
URLAlias string `json:"bridgeScriptUrl"`
Version string `json:"bridge_script_version"`
VersionAlias string `json:"bridgeScriptVersion"`
SHA256 string `json:"bridge_script_sha256"`
SHA256Alias string `json:"bridgeScriptSha256"`
SHA256AltAlias string `json:"bridge_script_hash"`
}
func bridgeScriptConfigFromPlatform(value any) bridgeScriptConfig {
var raw string
switch typed := value.(type) {
case gamedomain.Platform:
raw = typed.AdapterConfigJSON
case gamedomain.LaunchableGame:
raw = typed.AdapterConfigJSON
}
var config bridgeScriptConfig
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
config.URL = firstBridgeScriptValue(config.URL, config.URLAlias)
config.Version = firstBridgeScriptValue(config.Version, config.VersionAlias)
config.SHA256 = firstBridgeScriptValue(config.SHA256, config.SHA256Alias, config.SHA256AltAlias)
return config
}
func firstBridgeScriptValue(values ...string) string {
for _, value := range values {
normalized := strings.TrimSpace(value)
if normalized != "" {
return normalized
}
}
return ""
}
func appendBridgeScriptQuery(query url.Values, game gamedomain.LaunchableGame) {
config := bridgeScriptConfigFromPlatform(game)
if config.URL == "" {
return
}
// Flutter 从启动 URL 还原 bridgeConfig同时写 snake/camel 别名,避免 H5 或老客户端只识别其中一种命名。
query.Set("bridge_script_url", config.URL)
query.Set("bridgeScriptUrl", config.URL)
if config.Version != "" {
query.Set("bridge_script_version", config.Version)
query.Set("bridgeScriptVersion", config.Version)
}
if config.SHA256 != "" {
query.Set("bridge_script_sha256", config.SHA256)
query.Set("bridgeScriptSha256", config.SHA256)
}
appendBridgeKeys(query, "bridge_script_url", "bridge_script_version", "bridge_script_sha256")
}
func appendBridgeKeys(query url.Values, keys ...string) {
seen := make(map[string]bool)
values := make([]string, 0, len(keys)+1)
for _, raw := range strings.Split(query.Get("bridge_keys"), ",") {
key := strings.TrimSpace(raw)
if key == "" || seen[key] {
continue
}
seen[key] = true
values = append(values, key)
}
for _, raw := range keys {
key := strings.TrimSpace(raw)
if key == "" || seen[key] {
continue
}
seen[key] = true
values = append(values, key)
}
if len(values) > 0 {
query.Set("bridge_keys", strings.Join(values, ","))
}
}

View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"sync"
@ -29,6 +30,7 @@ const defaultScene = gamedomain.SceneVoiceRoom
type Repository interface {
Ping(ctx context.Context) error
ListGames(ctx context.Context, query ListGamesQuery) ([]gamedomain.AppGame, error)
ListRecentGames(ctx context.Context, query ListRecentGamesQuery) ([]gamedomain.AppGame, error)
GetLaunchableGame(ctx context.Context, appCode string, gameID string) (gamedomain.LaunchableGame, error)
GetPlatform(ctx context.Context, appCode string, platformCode string) (gamedomain.Platform, error)
CreateLaunchSession(ctx context.Context, session gamedomain.LaunchSession) error
@ -91,6 +93,17 @@ type ListGamesQuery struct {
ClientPlatform string
}
type ListRecentGamesQuery struct {
AppCode string
UserID int64
Scene string
RoomID string
RegionID int64
Language string
ClientPlatform string
PageSize int32
}
type ListCatalogQuery struct {
AppCode string
PlatformCode string
@ -120,6 +133,13 @@ type LaunchResult struct {
ServerTimeMS int64
}
type BridgeScriptResult struct {
URL string
Version string
SHA256 string
ServerTimeMS int64
}
// New 创建 game-service 用例层。
func New(config Config, repository Repository, wallet WalletClient, user UserClient, activity ...ActivityClient) *Service {
if config.LaunchSessionTTL <= 0 {
@ -167,6 +187,62 @@ func (s *Service) ListGames(ctx context.Context, query ListGamesQuery) ([]gamedo
return games, s.now().UnixMilli(), err
}
// ListRecentGames 返回当前用户最近启动过的语音房游戏,供 App 的 My Games tab 直接展示。
func (s *Service) ListRecentGames(ctx context.Context, query ListRecentGamesQuery) ([]gamedomain.AppGame, int64, error) {
if query.UserID <= 0 {
return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required")
}
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
query.Scene = normalizeScene(query.Scene)
query.Language = strings.ToLower(strings.TrimSpace(query.Language))
query.ClientPlatform = strings.ToLower(strings.TrimSpace(query.ClientPlatform))
query.PageSize = normalizeRecentGamesPageSize(query.PageSize)
ctx = appcode.WithContext(ctx, query.AppCode)
// 最近常玩只信任 game_launch_sessions 的真实启动事实,同时在仓储层重新套当前展示规则;
// 这样旧游戏下架、维护、区域不可见后不会继续出现在 My Games tab。
games, err := s.repository.ListRecentGames(ctx, query)
return games, s.now().UnixMilli(), err
}
// GetBridgeScript 返回 App 通用游戏桥接脚本配置,供客户端启动预热和打开游戏面板时做轻量刷新。
func (s *Service) GetBridgeScript(ctx context.Context, appCode string) (BridgeScriptResult, error) {
if s.repository == nil {
return BridgeScriptResult{}, xerr.New(xerr.Unavailable, "game repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
platforms, err := s.repository.ListPlatforms(ctx, appCode, gamedomain.StatusActive)
if err != nil {
return BridgeScriptResult{}, err
}
// admin 会把通用桥接脚本同步写入所有平台;这里仍按 sort/code 排序后读取第一份有效配置,
// 避免数据库返回顺序变化导致 App 在相同配置之间来回切换。
sort.SliceStable(platforms, func(left, right int) bool {
if platforms[left].SortOrder != platforms[right].SortOrder {
return platforms[left].SortOrder < platforms[right].SortOrder
}
return strings.Compare(platforms[left].PlatformCode, platforms[right].PlatformCode) < 0
})
for _, platform := range platforms {
config := bridgeScriptConfigFromPlatform(platform)
if strings.TrimSpace(config.URL) == "" {
continue
}
return BridgeScriptResult{
URL: strings.TrimSpace(config.URL),
Version: strings.TrimSpace(config.Version),
SHA256: strings.TrimSpace(config.SHA256),
ServerTimeMS: s.now().UnixMilli(),
}, nil
}
return BridgeScriptResult{ServerTimeMS: s.now().UnixMilli()}, nil
}
// LaunchGame 创建短期 H5 启动会话,并把原始钱包能力限制在服务端。
func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (LaunchResult, error) {
if command.UserID <= 0 || strings.TrimSpace(command.GameID) == "" {
@ -602,6 +678,16 @@ func normalizeScene(scene string) string {
return scene
}
func normalizeRecentGamesPageSize(pageSize int32) int32 {
if pageSize <= 0 {
return 4
}
if pageSize > 20 {
return 20
}
return pageSize
}
func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSession, token string) (string, error) {
base := strings.TrimSpace(game.APIBaseURL)
if strings.EqualFold(game.AdapterType, gamedomain.AdapterLeaderCCV1) && base == "" {
@ -670,6 +756,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
if session.RoomID != "" {
query.Set("platRoomId", session.RoomID)
}
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}
@ -686,6 +773,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
if session.RoomID != "" {
query.Set("roomid", session.RoomID)
}
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}
@ -713,6 +801,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
if config.Extra != "" {
query.Set("extra", config.Extra)
}
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}
@ -737,6 +826,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
query.Set("currencyIcon", config.CurrencyIcon)
}
query.Set("gsp", strconv.FormatInt(config.GSP, 10))
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}
@ -760,6 +850,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
query.Set("currencyIcon", config.CurrencyIcon)
query.Set("gameConfig", vivaGamesGameConfigQuery(config.CurrencyIcon))
}
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}
@ -773,6 +864,7 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
query.Set("display_user_id", session.DisplayUserID)
query.Set("scene", session.Scene)
query.Set("room_id", session.RoomID)
appendBridgeScriptQuery(query, game)
parsed.RawQuery = query.Encode()
return parsed.String(), nil
}

View File

@ -68,6 +68,66 @@ func TestLaunchGameCreatesSessionAndRejectsMaintenance(t *testing.T) {
}
}
func TestListRecentGamesNormalizesQueryAndDefaultsLimit(t *testing.T) {
repo := &fakeRepository{
recentGames: []gamedomain.AppGame{
{GameID: "viva_2", Name: "Rich Forever"},
},
}
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
svc.now = func() time.Time { return time.UnixMilli(1700000000000) }
games, serverTimeMS, err := svc.ListRecentGames(context.Background(), ListRecentGamesQuery{
UserID: 42,
Scene: "",
Language: " EN-US ",
ClientPlatform: " Android ",
})
if err != nil {
t.Fatalf("ListRecentGames failed: %v", err)
}
if serverTimeMS != 1700000000000 || len(games) != 1 || games[0].GameID != "viva_2" {
t.Fatalf("recent games response mismatch: time=%d games=%+v", serverTimeMS, games)
}
if repo.lastRecentQuery.AppCode != "lalu" || repo.lastRecentQuery.Scene != gamedomain.SceneVoiceRoom || repo.lastRecentQuery.PageSize != 4 || repo.lastRecentQuery.Language != "en-us" || repo.lastRecentQuery.ClientPlatform != "android" {
t.Fatalf("recent query normalization mismatch: %+v", repo.lastRecentQuery)
}
}
func TestGetBridgeScriptUsesFirstActivePlatformConfig(t *testing.T) {
repo := &fakeRepository{
platforms: []gamedomain.Platform{
{
AppCode: "lalu",
PlatformCode: "vivagames",
Status: gamedomain.StatusActive,
SortOrder: 20,
AdapterConfigJSON: `{"bridge_script_url":"https://cdn.example.test/bridge-viva.js","bridge_script_version":"20260608.2","bridge_script_sha256":"def"}`,
},
{
AppCode: "lalu",
PlatformCode: "baishun",
Status: gamedomain.StatusActive,
SortOrder: 10,
AdapterConfigJSON: `{"bridgeScriptUrl":"https://cdn.example.test/bridge.js","bridgeScriptVersion":"20260608.1","bridgeScriptSha256":"abc"}`,
},
},
}
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
svc.now = func() time.Time { return time.UnixMilli(1700000000000) }
result, err := svc.GetBridgeScript(context.Background(), " LALU ")
if err != nil {
t.Fatalf("GetBridgeScript failed: %v", err)
}
if result.URL != "https://cdn.example.test/bridge.js" || result.Version != "20260608.1" || result.SHA256 != "abc" || result.ServerTimeMS != 1700000000000 {
t.Fatalf("bridge script mismatch: %+v", result)
}
if repo.lastListPlatformsAppCode != "lalu" || repo.lastListPlatformsStatus != gamedomain.StatusActive {
t.Fatalf("ListPlatforms query mismatch: app=%q status=%q", repo.lastListPlatformsAppCode, repo.lastListPlatformsStatus)
}
}
func TestLaunchGameBuildsYomiAuthURL(t *testing.T) {
repo := &fakeRepository{
launchable: gamedomain.LaunchableGame{
@ -359,6 +419,9 @@ func TestLaunchGameBuildsVivaGamesURL(t *testing.T) {
"bgm_switch":0,
"gsp":101,
"currency_icon":"https://cdn.example/coin.png",
"bridge_script_url":"https://cdn.example/game-bridge/voice-room-game-bridge.v1.js",
"bridge_script_version":"20260608.1",
"bridge_script_sha256":"78f0d2d0b52d5711c1f2a9e8d3af8a9d3db7e03c83f5bd8d2b8e629f1f5b6c13",
"game_urls":{"2":"https://dev-rich2.s3.ap-southeast-1.amazonaws.com/richForever.html?game_id=2&isShow=1"}
}`,
},
@ -389,19 +452,25 @@ func TestLaunchGameBuildsVivaGamesURL(t *testing.T) {
}
query := parsed.Query()
expected := map[string]string{
"appId": "1012127974",
"userId": "420001",
"code": "app_access_token_viva",
"gameId": "2",
"metadata": `{"room_id":"room_1"}`,
"language": "en-US",
"coinType": "0",
"amountRate": "1",
"bgmSwitch": "0",
"gsp": "101",
"currencyIcon": "https://cdn.example/coin.png",
"gameConfig": `{"currencyIcon":"https://cdn.example/coin.png"}`,
"bridge_keys": "appId,userId,code,metadata,language,coinType,amountRate,bgmSwitch,gsp,currencyIcon,gameConfig,gameId",
"appId": "1012127974",
"userId": "420001",
"code": "app_access_token_viva",
"gameId": "2",
"metadata": `{"room_id":"room_1"}`,
"language": "en-US",
"coinType": "0",
"amountRate": "1",
"bgmSwitch": "0",
"gsp": "101",
"currencyIcon": "https://cdn.example/coin.png",
"gameConfig": `{"currencyIcon":"https://cdn.example/coin.png"}`,
"bridge_keys": "appId,userId,code,metadata,language,coinType,amountRate,bgmSwitch,gsp,currencyIcon,gameConfig,gameId,bridge_script_url,bridge_script_version,bridge_script_sha256",
"bridge_script_url": "https://cdn.example/game-bridge/voice-room-game-bridge.v1.js",
"bridgeScriptUrl": "https://cdn.example/game-bridge/voice-room-game-bridge.v1.js",
"bridge_script_version": "20260608.1",
"bridgeScriptVersion": "20260608.1",
"bridge_script_sha256": "78f0d2d0b52d5711c1f2a9e8d3af8a9d3db7e03c83f5bd8d2b8e629f1f5b6c13",
"bridgeScriptSha256": "78f0d2d0b52d5711c1f2a9e8d3af8a9d3db7e03c83f5bd8d2b8e629f1f5b6c13",
}
for key, want := range expected {
if got := query.Get(key); got != want {
@ -1359,21 +1428,33 @@ func TestProcessLevelEventOutboxBatchRelaysGameSpend(t *testing.T) {
}
type fakeRepository struct {
launchable gamedomain.LaunchableGame
platform gamedomain.Platform
session gamedomain.LaunchSession
order gamedomain.GameOrder
orders []gamedomain.GameOrder
repair gamedomain.RepairOrder
levelEvents []gamedomain.LevelEventOutbox
deliveredEvents []string
failedEvents []string
launchable gamedomain.LaunchableGame
platform gamedomain.Platform
session gamedomain.LaunchSession
order gamedomain.GameOrder
orders []gamedomain.GameOrder
repair gamedomain.RepairOrder
levelEvents []gamedomain.LevelEventOutbox
recentGames []gamedomain.AppGame
lastRecentQuery ListRecentGamesQuery
platforms []gamedomain.Platform
lastListPlatformsAppCode string
lastListPlatformsStatus string
deliveredEvents []string
failedEvents []string
}
func (f *fakeRepository) Ping(context.Context) error { return nil }
func (f *fakeRepository) ListGames(context.Context, ListGamesQuery) ([]gamedomain.AppGame, error) {
return []gamedomain.AppGame{{GameID: "demo_rocket_001"}}, nil
}
func (f *fakeRepository) ListRecentGames(_ context.Context, query ListRecentGamesQuery) ([]gamedomain.AppGame, error) {
f.lastRecentQuery = query
if f.recentGames != nil {
return f.recentGames, nil
}
return []gamedomain.AppGame{{GameID: "demo_recent_001"}}, nil
}
func (f *fakeRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) {
return f.launchable, nil
}
@ -1472,7 +1553,12 @@ func (f *fakeRepository) MarkLevelEventFailed(_ context.Context, eventID string,
f.failedEvents = append(f.failedEvents, eventID)
return nil
}
func (f *fakeRepository) ListPlatforms(context.Context, string, string) ([]gamedomain.Platform, error) {
func (f *fakeRepository) ListPlatforms(_ context.Context, appCode string, status string) ([]gamedomain.Platform, error) {
f.lastListPlatformsAppCode = appCode
f.lastListPlatformsStatus = status
if f.platforms != nil {
return f.platforms, nil
}
return nil, nil
}
func (f *fakeRepository) UpsertPlatform(context.Context, gamedomain.Platform) (gamedomain.Platform, error) {

View File

@ -404,6 +404,67 @@ func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQ
return items, rows.Err()
}
func (r *Repository) ListRecentGames(ctx context.Context, query gameservice.ListRecentGamesQuery) ([]gamedomain.AppGame, error) {
pageSize := int(query.PageSize)
if pageSize <= 0 {
pageSize = 4
}
// My Games 先按当前用户的启动会话聚合出每个游戏的最后一次启动时间,
// 再套当前 catalog、platform 和 display_rules保证最近玩过但已下架或当前区域不可见的游戏不会返回。
rows, err := r.db.QueryContext(ctx,
`SELECT c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status,
MIN(CASE WHEN r.sort_order = 0 THEN c.sort_order ELSE r.sort_order END) AS display_sort,
recent.last_played_at_ms
FROM (
SELECT game_id, MAX(created_at_ms) AS last_played_at_ms
FROM game_launch_sessions
WHERE app_code = ?
AND user_id = ?
AND scene = ?
GROUP BY game_id
) recent
JOIN game_catalog c ON c.app_code = ? AND c.game_id = recent.game_id
JOIN game_platforms p ON p.app_code = c.app_code AND p.platform_code = c.platform_code
JOIN game_display_rules r ON r.app_code = c.app_code AND r.game_id = c.game_id
WHERE c.status IN ('active', 'maintenance')
AND p.status IN ('active', 'maintenance')
AND r.scene = ?
AND r.enabled = 1
AND r.visible = 1
AND (r.region_id = 0 OR r.region_id = ?)
AND (r.language = '' OR r.language = ?)
AND (r.platform = '' OR r.platform = ?)
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status,
c.sort_order, p.sort_order, recent.last_played_at_ms
ORDER BY recent.last_played_at_ms DESC, display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC
LIMIT ?`,
appcode.Normalize(query.AppCode), query.UserID, query.Scene,
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform, pageSize,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []gamedomain.AppGame{}
for rows.Next() {
var item gamedomain.AppGame
var gameStatus, platformStatus string
var displaySort int32
var lastPlayedAtMS int64
if err := rows.Scan(&item.GameID, &item.PlatformCode, &item.Name, &item.IconURL, &item.CoverURL, &item.Category, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &gameStatus, &platformStatus, &displaySort, &lastPlayedAtMS); err != nil {
return nil, err
}
item.NameKey = "game." + item.GameID + ".name"
item.SortOrder = displaySort
item.Enabled = gameStatus == gamedomain.StatusActive && platformStatus == gamedomain.StatusActive
item.Maintenance = gameStatus == gamedomain.StatusMaintenance || platformStatus == gamedomain.StatusMaintenance
items = append(items, item)
}
return items, rows.Err()
}
func (r *Repository) GetLaunchableGame(ctx context.Context, appCode string, gameID string) (gamedomain.LaunchableGame, error) {
// 启动游戏时一次性取出 catalog + platform 配置,避免拿到游戏后再查平台导致配置不一致。
row := r.db.QueryRowContext(ctx,

View File

@ -47,6 +47,44 @@ func (s *Server) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*
return resp, nil
}
func (s *Server) ListRecentGames(ctx context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
items, serverTimeMS, err := s.svc.ListRecentGames(ctx, gameservice.ListRecentGamesQuery{
AppCode: req.GetMeta().GetAppCode(),
UserID: req.GetUserId(),
Scene: req.GetScene(),
RoomID: req.GetRoomId(),
RegionID: req.GetRegionId(),
Language: req.GetLanguage(),
ClientPlatform: req.GetClientPlatform(),
PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &gamev1.ListGamesResponse{ServerTimeMs: serverTimeMS, Games: make([]*gamev1.AppGame, 0, len(items))}
for _, item := range items {
resp.Games = append(resp.Games, appGameToProto(item))
}
return resp, nil
}
func (s *Server) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.GetBridgeScript(ctx, req.GetMeta().GetAppCode())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &gamev1.GetBridgeScriptResponse{
Config: &gamev1.GameBridgeScriptConfig{
BridgeScriptUrl: result.URL,
BridgeScriptVersion: result.Version,
BridgeScriptSha256: result.SHA256,
},
ServerTimeMs: result.ServerTimeMS,
}, nil
}
func (s *Server) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.LaunchGame(ctx, gameservice.LaunchCommand{
@ -195,6 +233,9 @@ func (unavailableRepository) Ping(context.Context) error {
func (unavailableRepository) ListGames(context.Context, gameservice.ListGamesQuery) ([]gamedomain.AppGame, error) {
return nil, xerr.New(xerr.Unavailable, "game repository is not configured")
}
func (unavailableRepository) ListRecentGames(context.Context, gameservice.ListRecentGamesQuery) ([]gamedomain.AppGame, error) {
return nil, xerr.New(xerr.Unavailable, "game repository is not configured")
}
func (unavailableRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) {
return gamedomain.LaunchableGame{}, xerr.New(xerr.Unavailable, "game repository is not configured")
}

View File

@ -89,6 +89,7 @@ func New(cfg config.Config) (*App, error) {
var userProfileClient client.UserProfileClient = client.NewGRPCUserProfileClient(userConn)
var userDeviceClient client.UserDeviceClient = client.NewGRPCUserDeviceClient(userConn)
var userCountryQueryClient client.UserCountryQueryClient = client.NewGRPCUserCountryQueryClient(userConn)
var userRegionClient client.UserRegionClient = client.NewGRPCUserRegionClient(userConn)
var userHostClient client.UserHostClient = client.NewGRPCUserHostClient(userConn)
var userSocialClient client.UserSocialClient = client.NewGRPCUserSocialClient(userConn)
var userCPClient client.UserCPClient = client.NewGRPCUserCPClient(userConn)
@ -157,6 +158,7 @@ func New(cfg config.Config) (*App, error) {
handler.SetRoomQueryClient(roomQueryClient)
handler.SetUserDeviceClient(userDeviceClient)
handler.SetUserCountryQueryClient(userCountryQueryClient)
handler.SetUserRegionClient(userRegionClient)
handler.SetUserHostClient(userHostClient)
handler.SetUserSocialClient(userSocialClient)
handler.SetUserCPClient(userCPClient)

View File

@ -10,6 +10,8 @@ import (
// GameClient 抽象 gateway 对 game-service App 与回调 RPC 的依赖。
type GameClient interface {
ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error)
ListRecentGames(ctx context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error)
GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error)
LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error)
HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error)
}
@ -30,6 +32,14 @@ func (c *grpcGameClient) ListGames(ctx context.Context, req *gamev1.ListGamesReq
return c.app.ListGames(ctx, req)
}
func (c *grpcGameClient) ListRecentGames(ctx context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) {
return c.app.ListRecentGames(ctx, req)
}
func (c *grpcGameClient) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) {
return c.app.GetBridgeScript(ctx, req)
}
func (c *grpcGameClient) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) {
return c.app.LaunchGame(ctx, req)
}

View File

@ -13,6 +13,7 @@ type GrowthLevelClient interface {
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)
SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error)
}
type grpcGrowthLevelClient struct {
@ -38,3 +39,7 @@ func (c *grpcGrowthLevelClient) BatchGetUserLevelDisplayProfiles(ctx context.Con
func (c *grpcGrowthLevelClient) ListLevelRewards(ctx context.Context, req *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) {
return c.client.ListLevelRewards(ctx, req)
}
func (c *grpcGrowthLevelClient) SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
return c.client.SetUserLevel(ctx, req)
}

View File

@ -37,6 +37,9 @@ type UserProfileClient interface {
CompleteOnboarding(ctx context.Context, req *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error)
UpdateUserProfile(ctx context.Context, req *userv1.UpdateUserProfileRequest) (*userv1.UpdateUserProfileResponse, error)
ChangeUserCountry(ctx context.Context, req *userv1.ChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error)
CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error)
ListManagerUserBlocks(ctx context.Context, req *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error)
UnblockManagerUser(ctx context.Context, req *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error)
}
// UserSocialClient 抽象 gateway 对 user-service 访问、关注和好友关系能力的依赖。
@ -60,6 +63,9 @@ type UserCPClient interface {
AcceptCPApplication(ctx context.Context, req *userv1.AcceptCPApplicationRequest) (*userv1.AcceptCPApplicationResponse, error)
RejectCPApplication(ctx context.Context, req *userv1.RejectCPApplicationRequest) (*userv1.RejectCPApplicationResponse, error)
ListCPRelationships(ctx context.Context, req *userv1.ListCPRelationshipsRequest) (*userv1.ListCPRelationshipsResponse, error)
PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error)
ConfirmBreakCPRelationship(ctx context.Context, req *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error)
CancelBreakCPRelationship(ctx context.Context, req *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error)
}
// UserDeviceClient 抽象 gateway 对 user-service 设备 push token 的依赖。
@ -74,6 +80,11 @@ type UserCountryQueryClient interface {
ListLoginRiskBlockedCountries(ctx context.Context, req *userv1.ListLoginRiskBlockedCountriesRequest) (*userv1.ListLoginRiskBlockedCountriesResponse, error)
}
// UserRegionClient 抽象 gateway 对 user-service 区域国家归属查询的依赖。
type UserRegionClient interface {
GetRegion(ctx context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error)
}
// UserHostClient 抽象 gateway 对 user-service host domain 身份查询能力的依赖。
type UserHostClient interface {
SearchAgencies(ctx context.Context, req *userv1.SearchAgenciesRequest) (*userv1.SearchAgenciesResponse, error)
@ -129,6 +140,10 @@ type grpcUserCountryQueryClient struct {
client userv1.CountryQueryServiceClient
}
type grpcUserRegionClient struct {
client userv1.RegionAdminServiceClient
}
type grpcUserHostClient struct {
client userv1.UserHostServiceClient
}
@ -186,6 +201,13 @@ func NewGRPCUserCountryQueryClient(conn *grpc.ClientConn) UserCountryQueryClient
}
}
// NewGRPCUserRegionClient 用 gRPC 连接构造 user-service 区域查询 client。
func NewGRPCUserRegionClient(conn *grpc.ClientConn) UserRegionClient {
return &grpcUserRegionClient{
client: userv1.NewRegionAdminServiceClient(conn),
}
}
// NewGRPCUserHostClient 用 gRPC 连接构造 user-service host domain client。
func NewGRPCUserHostClient(conn *grpc.ClientConn) UserHostClient {
return &grpcUserHostClient{
@ -276,6 +298,18 @@ func (c *grpcUserProfileClient) ChangeUserCountry(ctx context.Context, req *user
return c.client.ChangeUserCountry(ctx, req)
}
func (c *grpcUserProfileClient) CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
return c.client.CreateManagerUserBlock(ctx, req)
}
func (c *grpcUserProfileClient) ListManagerUserBlocks(ctx context.Context, req *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error) {
return c.client.ListManagerUserBlocks(ctx, req)
}
func (c *grpcUserProfileClient) UnblockManagerUser(ctx context.Context, req *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error) {
return c.client.UnblockManagerUser(ctx, req)
}
func (c *grpcUserSocialClient) RecordProfileVisit(ctx context.Context, req *userv1.RecordProfileVisitRequest) (*userv1.RecordProfileVisitResponse, error) {
return c.client.RecordProfileVisit(ctx, req)
}
@ -336,6 +370,18 @@ func (c *grpcUserCPClient) ListCPRelationships(ctx context.Context, req *userv1.
return c.client.ListCPRelationships(ctx, req)
}
func (c *grpcUserCPClient) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) {
return c.client.PrepareBreakCPRelationship(ctx, req)
}
func (c *grpcUserCPClient) ConfirmBreakCPRelationship(ctx context.Context, req *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error) {
return c.client.ConfirmBreakCPRelationship(ctx, req)
}
func (c *grpcUserCPClient) CancelBreakCPRelationship(ctx context.Context, req *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error) {
return c.client.CancelBreakCPRelationship(ctx, req)
}
func (c *grpcUserDeviceClient) BindPushToken(ctx context.Context, req *userv1.BindPushTokenRequest) (*userv1.BindPushTokenResponse, error) {
return c.client.BindPushToken(ctx, req)
}
@ -352,6 +398,10 @@ func (c *grpcUserCountryQueryClient) ListLoginRiskBlockedCountries(ctx context.C
return c.client.ListLoginRiskBlockedCountries(ctx, req)
}
func (c *grpcUserRegionClient) GetRegion(ctx context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error) {
return c.client.GetRegion(ctx, req)
}
func (c *grpcUserHostClient) SearchAgencies(ctx context.Context, req *userv1.SearchAgenciesRequest) (*userv1.SearchAgenciesResponse, error) {
return c.client.SearchAgencies(ctx, req)
}

View File

@ -23,6 +23,8 @@ type WalletClient interface {
ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error)
GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error)
PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error)
DebitCPBreakupFee(ctx context.Context, req *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error)
GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error)
TransferCoinFromSeller(ctx context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error)
ListCoinSellerSalaryExchangeRateTiers(ctx context.Context, req *walletv1.ListCoinSellerSalaryExchangeRateTiersRequest) (*walletv1.ListCoinSellerSalaryExchangeRateTiersResponse, error)
ExchangeSalaryToCoin(ctx context.Context, req *walletv1.ExchangeSalaryToCoinRequest) (*walletv1.ExchangeSalaryToCoinResponse, error)
@ -107,6 +109,14 @@ func (c *grpcWalletClient) PurchaseVip(ctx context.Context, req *walletv1.Purcha
return c.client.PurchaseVip(ctx, req)
}
func (c *grpcWalletClient) DebitCPBreakupFee(ctx context.Context, req *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error) {
return c.client.DebitCPBreakupFee(ctx, req)
}
func (c *grpcWalletClient) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
return c.client.GrantVip(ctx, req)
}
func (c *grpcWalletClient) TransferCoinFromSeller(ctx context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error) {
return c.client.TransferCoinFromSeller(ctx, req)
}

View File

@ -175,6 +175,7 @@ func (h *Handler) listWeeklyStarHistory(writer http.ResponseWriter, request *htt
for _, cycle := range resp.GetCycles() {
cycleItems = append(cycleItems, cycle.GetCycle())
ids = append(ids, weeklyStarUserIDs(cycle.GetTopEntries(), nil)...)
cycleItems = append(cycleItems, cycle.GetCycle())
}
users := h.resolveWeeklyStarUsers(request, ids)
resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, cycleItems...)

View File

@ -54,6 +54,81 @@ func TestListGamesUsesAuthenticatedUserRegionAndReturnsEnvelope(t *testing.T) {
}
}
func TestListRecentGamesUsesAuthenticatedUserRegionAndLimit(t *testing.T) {
gameClient := &fakeGatewayGameClient{recentResp: &gamev1.ListGamesResponse{
Games: []*gamev1.AppGame{{
GameId: "viva_2",
PlatformCode: "vivagames",
Name: "Rich Forever",
LaunchMode: "h5_popup",
Orientation: "portrait",
Enabled: true,
}},
ServerTimeMs: 1700000000000,
}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001})
handler.SetGameClient(gameClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/games/recent?scene=voice_room&room_id=room-1&limit=4", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-App-Language", "en")
request.Header.Set("X-App-Platform", "android")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if gameClient.lastRecent.GetUserId() != 42 || gameClient.lastRecent.GetRegionId() != 1001 || gameClient.lastRecent.GetScene() != "voice_room" || gameClient.lastRecent.GetRoomId() != "room-1" || gameClient.lastRecent.GetPageSize() != 4 || gameClient.lastRecent.GetClientPlatform() != "android" {
t.Fatalf("ListRecentGames request mismatch: %+v", gameClient.lastRecent)
}
var envelope httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := envelope.Data.(map[string]any)
games := data["games"].([]any)
first := games[0].(map[string]any)
if first["platform_code"] != "vivagames" || first["game_id"] != "viva_2" {
t.Fatalf("recent game response mismatch: %+v", first)
}
}
func TestGetBridgeScriptReturnsUniversalScriptConfig(t *testing.T) {
gameClient := &fakeGatewayGameClient{bridgeResp: &gamev1.GetBridgeScriptResponse{
Config: &gamev1.GameBridgeScriptConfig{
BridgeScriptUrl: "https://cdn.example.test/game-bridge.js",
BridgeScriptVersion: "20260608.1",
BridgeScriptSha256: "abc",
},
ServerTimeMs: 1700000000000,
}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001})
handler.SetGameClient(gameClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/games/bridge-script", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if gameClient.lastBridge.GetMeta().GetActorUserId() != 42 {
t.Fatalf("GetBridgeScript request mismatch: %+v", gameClient.lastBridge)
}
var envelope httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
t.Fatalf("decode response failed: %v", err)
}
data := envelope.Data.(map[string]any)
if data["bridge_script_url"] != "https://cdn.example.test/game-bridge.js" || data["bridgeScriptUrl"] != "https://cdn.example.test/game-bridge.js" || data["bridge_script_version"] != "20260608.1" || data["bridge_script_sha256"] != "abc" {
t.Fatalf("bridge script response mismatch: %+v", data)
}
}
func TestLaunchGameForwardsDisplayUserID(t *testing.T) {
gameClient := &fakeGatewayGameClient{launchResp: &gamev1.LaunchGameResponse{
SessionId: "game_sess_1",
@ -100,9 +175,13 @@ func TestGameCallbackIsRawPublicResponse(t *testing.T) {
type fakeGatewayGameClient struct {
listResp *gamev1.ListGamesResponse
recentResp *gamev1.ListGamesResponse
bridgeResp *gamev1.GetBridgeScriptResponse
launchResp *gamev1.LaunchGameResponse
callbackResp *gamev1.CallbackResponse
lastList *gamev1.ListGamesRequest
lastRecent *gamev1.ListRecentGamesRequest
lastBridge *gamev1.GetBridgeScriptRequest
lastLaunch *gamev1.LaunchGameRequest
lastCallback *gamev1.CallbackRequest
}
@ -115,6 +194,22 @@ func (f *fakeGatewayGameClient) ListGames(_ context.Context, req *gamev1.ListGam
return &gamev1.ListGamesResponse{}, nil
}
func (f *fakeGatewayGameClient) ListRecentGames(_ context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) {
f.lastRecent = req
if f.recentResp != nil {
return f.recentResp, nil
}
return &gamev1.ListGamesResponse{}, nil
}
func (f *fakeGatewayGameClient) GetBridgeScript(_ context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) {
f.lastBridge = req
if f.bridgeResp != nil {
return f.bridgeResp, nil
}
return &gamev1.GetBridgeScriptResponse{}, nil
}
func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) {
f.lastLaunch = req
if f.launchResp != nil {

View File

@ -40,6 +40,52 @@ func (h *Handler) listGames(writer http.ResponseWriter, request *http.Request) {
httpkit.Write(writer, request, gameListDataFromProto(resp), err)
}
func (h *Handler) listRecentGames(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
pageSize, ok := recentGamePageSize(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
// 最近常玩仍传 scene、room、region、language 和 platform让 game-service 在真实启动记录之外,
// 继续按当前房间场景和用户区域过滤,避免 App 展示用户已不能进入的游戏。
resp, err := h.gameClient.ListRecentGames(request.Context(), &gamev1.ListRecentGamesRequest{
Meta: gameMeta(request),
UserId: userID,
Scene: strings.TrimSpace(request.URL.Query().Get("scene")),
RoomId: strings.TrimSpace(request.URL.Query().Get("room_id")),
RegionId: userResp.GetUser().GetRegionId(),
Language: requestLanguage(request),
ClientPlatform: httpkit.FirstHeader(request, "X-App-Platform", "X-Platform"),
PageSize: pageSize,
})
httpkit.Write(writer, request, gameListDataFromProto(resp), err)
}
func (h *Handler) getBridgeScript(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
// 桥接脚本配置是 App 级启动预热信息,不依赖房间和用户区域;保持接口轻量,避免拖慢进 App 主流程。
resp, err := h.gameClient.GetBridgeScript(request.Context(), &gamev1.GetBridgeScriptRequest{
Meta: gameMeta(request),
})
httpkit.Write(writer, request, gameBridgeScriptDataFromProto(resp), err)
}
func (h *Handler) launchGame(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
@ -133,6 +179,13 @@ func requestLanguage(request *http.Request) string {
return strings.TrimSpace(value)
}
func recentGamePageSize(request *http.Request) (int32, bool) {
if strings.TrimSpace(request.URL.Query().Get("limit")) != "" {
return httpkit.PositiveInt32Query(request, "limit", 4)
}
return httpkit.PositiveInt32Query(request, "page_size", 4)
}
func bearerAccessToken(request *http.Request) string {
header := strings.TrimSpace(request.Header.Get("Authorization"))
if !strings.HasPrefix(header, "Bearer ") {

View File

@ -35,6 +35,16 @@ type gameLaunchData struct {
ServerTimeMS int64 `json:"server_time_ms"`
}
type gameBridgeScriptData struct {
BridgeScriptURL string `json:"bridge_script_url"`
BridgeScriptURLV2 string `json:"bridgeScriptUrl"`
BridgeScriptVersion string `json:"bridge_script_version"`
BridgeScriptVersionV2 string `json:"bridgeScriptVersion"`
BridgeScriptSHA256 string `json:"bridge_script_sha256"`
BridgeScriptSHA256V2 string `json:"bridgeScriptSha256"`
ServerTimeMS int64 `json:"server_time_ms"`
}
func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData {
if resp == nil {
return gameListData{}
@ -76,3 +86,19 @@ func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData {
ServerTimeMS: resp.GetServerTimeMs(),
}
}
func gameBridgeScriptDataFromProto(resp *gamev1.GetBridgeScriptResponse) gameBridgeScriptData {
if resp == nil {
return gameBridgeScriptData{}
}
config := resp.GetConfig()
return gameBridgeScriptData{
BridgeScriptURL: config.GetBridgeScriptUrl(),
BridgeScriptURLV2: config.GetBridgeScriptUrl(),
BridgeScriptVersion: config.GetBridgeScriptVersion(),
BridgeScriptVersionV2: config.GetBridgeScriptVersion(),
BridgeScriptSHA256: config.GetBridgeScriptSha256(),
BridgeScriptSHA256V2: config.GetBridgeScriptSha256(),
ServerTimeMS: resp.GetServerTimeMs(),
}
}

View File

@ -28,9 +28,11 @@ func New(config Config) *Handler {
func (h *Handler) Handlers() httproutes.GameHandlers {
return httproutes.GameHandlers{
ListGames: h.listGames,
LaunchGame: h.launchGame,
HandleCallback: h.handleGameCallback,
ListGames: h.listGames,
ListRecentGames: h.listRecentGames,
GetBridgeScript: h.getBridgeScript,
LaunchGame: h.launchGame,
HandleCallback: h.handleGameCallback,
}
}
@ -38,6 +40,14 @@ func (h *Handler) ListGames(writer http.ResponseWriter, request *http.Request) {
h.listGames(writer, request)
}
func (h *Handler) ListRecentGames(writer http.ResponseWriter, request *http.Request) {
h.listRecentGames(writer, request)
}
func (h *Handler) GetBridgeScript(writer http.ResponseWriter, request *http.Request) {
h.getBridgeScript(writer, request)
}
func (h *Handler) LaunchGame(writer http.ResponseWriter, request *http.Request) {
h.launchGame(writer, request)
}

View File

@ -32,6 +32,7 @@ type Handler struct {
userCPClient client.UserCPClient
userDeviceClient client.UserDeviceClient
userCountryClient client.UserCountryQueryClient
userRegionClient client.UserRegionClient
userHostClient client.UserHostClient
appRegistryClient client.AppRegistryClient
walletClient client.WalletClient
@ -161,6 +162,11 @@ func (h *Handler) SetUserCountryQueryClient(userCountryClient client.UserCountry
h.userCountryClient = userCountryClient
}
// SetUserRegionClient 注入 user-service 区域国家归属查询 client。
func (h *Handler) SetUserRegionClient(userRegionClient client.UserRegionClient) {
h.userRegionClient = userRegionClient
}
// SetUserDeviceClient 注入 user-service 设备推送 token client。
func (h *Handler) SetUserDeviceClient(userDeviceClient client.UserDeviceClient) {
h.userDeviceClient = userDeviceClient

View File

@ -102,6 +102,7 @@ type UserHandlers struct {
AcceptCPApplication http.HandlerFunc
RejectCPApplication http.HandlerFunc
ListCPRelationships http.HandlerFunc
BreakCPRelationship http.HandlerFunc
GetMyProfile http.HandlerFunc
GetMyAppearance http.HandlerFunc
GetUserAppearance http.HandlerFunc
@ -116,6 +117,14 @@ type ManagerHandlers struct {
ListManagerGrantResources http.HandlerFunc
GrantManagerResource http.HandlerFunc
LookupBusinessUsers http.HandlerFunc
GetManagerOverview http.HandlerFunc
SearchManagerUsers http.HandlerFunc
GrantManagerVIP http.HandlerFunc
ManagerBlocks http.HandlerFunc
ListManagerBlocks http.HandlerFunc
CreateManagerBlock http.HandlerFunc
UnblockManagerBlock http.HandlerFunc
SetManagerUserLevel http.HandlerFunc
}
type RoomHandlers struct {
@ -227,9 +236,11 @@ type VIPHandlers struct {
}
type GameHandlers struct {
ListGames http.HandlerFunc
LaunchGame http.HandlerFunc
HandleCallback http.HandlerFunc
ListGames http.HandlerFunc
ListRecentGames http.HandlerFunc
GetBridgeScript http.HandlerFunc
LaunchGame http.HandlerFunc
HandleCallback http.HandlerFunc
}
type routes struct {
@ -356,6 +367,7 @@ func (r routes) registerUserRoutes() {
r.profile("/cp/applications/{application_id}/accept", http.MethodPost, h.AcceptCPApplication)
r.profile("/cp/applications/{application_id}/reject", http.MethodPost, h.RejectCPApplication)
r.profile("/cp/relationships", http.MethodGet, h.ListCPRelationships)
r.profile("/cp/relationships/{relationship_id}/break", http.MethodPost, h.BreakCPRelationship)
r.profile("/appearance", http.MethodGet, h.GetMyAppearance)
r.profile("/users/me/appearance", http.MethodGet, h.GetMyAppearance)
r.profile("/users/me", "", h.GetMyProfile)
@ -368,8 +380,14 @@ func (r routes) registerUserRoutes() {
func (r routes) registerManagerRoutes() {
h := r.config.Manager
r.profile("/manager-center/overview", http.MethodGet, h.GetManagerOverview)
r.profile("/manager-center/users/search", http.MethodGet, h.SearchManagerUsers)
r.profile("/manager-center/resource-grants/resources", http.MethodGet, h.ListManagerGrantResources)
r.profile("/manager-center/resource-grants", http.MethodPost, h.GrantManagerResource)
r.profile("/manager-center/vip-grants", http.MethodPost, h.GrantManagerVIP)
r.profile("/manager-center/blocks", "", h.ManagerBlocks)
r.profile("/manager-center/blocks/{block_id}/unblock", http.MethodPost, h.UnblockManagerBlock)
r.profile("/manager-center/levels", http.MethodPost, h.SetManagerUserLevel)
r.profile("/business/users/lookup", http.MethodGet, h.LookupBusinessUsers)
}
@ -493,6 +511,8 @@ func (r routes) registerVIPRoutes() {
func (r routes) registerGameRoutes() {
h := r.config.Game
r.profile("/games", http.MethodGet, h.ListGames)
r.profile("/games/recent", http.MethodGet, h.ListRecentGames)
r.profile("/games/bridge-script", http.MethodGet, h.GetBridgeScript)
r.profile("/games/{game_id}/launch", http.MethodPost, h.LaunchGame)
r.public("/game-callbacks/{platform_code}/{operation}", http.MethodPost, h.HandleCallback)
}

View File

@ -2,12 +2,14 @@ package http
import (
"bytes"
"context"
"encoding/json"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
"net/http"
"net/http/httptest"
"testing"
activityv1 "hyapp.local/api/proto/activity/v1"
userv1 "hyapp.local/api/proto/user/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/services/gateway-service/internal/auth"
@ -20,6 +22,14 @@ func newManagerCenterTestRouter(walletClient *fakeWalletClient, hostClient *fake
return handler.Routes(auth.NewVerifier("secret"))
}
func newManagerCenterTestRouterWithGrowth(walletClient *fakeWalletClient, hostClient *fakeUserHostClient, profileClient *fakeUserProfileClient, growthClient *fakeGrowthLevelClient) http.Handler {
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
handler.SetWalletClient(walletClient)
handler.SetUserHostClient(hostClient)
handler.SetGrowthLevelClient(growthClient)
return handler.Routes(auth.NewVerifier("secret"))
}
func TestListManagerGrantResourcesChecksCapabilityAndFiltersWalletRequest(t *testing.T) {
walletClient := &fakeWalletClient{
listResourcesResp: &walletv1.ListResourcesResponse{
@ -164,24 +174,190 @@ func TestGrantManagerResourceUsesServerControlledGrantShape(t *testing.T) {
if profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 900001 {
t.Fatalf("target user lookup mismatch: %+v", profileClient.lastGet)
}
if req := walletClient.lastGrantResource; req == nil || req.GetCommandId() != "mgr-grant-1" || req.GetTargetUserId() != 900001 || req.GetResourceId() != 101 || req.GetQuantity() != 1 || req.GetDurationMs() != 0 || req.GetOperatorUserId() != 42 || req.GetGrantSource() != "manager_center" || req.GetReason() != "manager_center" {
if req := walletClient.lastGrantResource; req == nil || req.GetCommandId() != "mgr-grant-1" || req.GetTargetUserId() != 900001 || req.GetResourceId() != 101 || req.GetQuantity() != 1 || req.GetDurationMs() != 604800000 || req.GetOperatorUserId() != 42 || req.GetGrantSource() != "manager_center" || req.GetReason() != "manager_center" {
t.Fatalf("wallet GrantResource request mismatch: %+v", req)
}
}
func TestGrantManagerResourceRejectsClientComputedQuantityOrDuration(t *testing.T) {
func TestGrantManagerResourceAllowsConfiguredDuration(t *testing.T) {
walletClient := &fakeWalletClient{}
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
body := []byte(`{"command_id":"mgr-grant-1","target_user_id":"900001","resource_id":"101","quantity":9}`)
body := []byte(`{"command_id":"mgr-grant-30","target_user_id":"900001","resource_id":"101","duration_ms":2592000000}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/resource-grants", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-grant-shape")
request.Header.Set("X-Request-ID", "req-manager-grant-duration")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-manager-grant-shape")
if walletClient.lastGrantResource != nil {
t.Fatalf("client computed grant shape must not reach wallet-service: %+v", walletClient.lastGrantResource)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if req := walletClient.lastGrantResource; req == nil || req.GetDurationMs() != 2592000000 {
t.Fatalf("manager grant duration was not forwarded: %+v", req)
}
}
func TestGrantManagerResourceRejectsClientComputedQuantityOrInvalidDuration(t *testing.T) {
walletClient := &fakeWalletClient{}
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
cases := []struct {
name string
body string
}{
{name: "quantity", body: `{"command_id":"mgr-grant-1","target_user_id":"900001","resource_id":"101","quantity":9}`},
{name: "duration", body: `{"command_id":"mgr-grant-1","target_user_id":"900001","resource_id":"101","duration_ms":86400000}`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
walletClient.lastGrantResource = nil
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/resource-grants", bytes.NewReader([]byte(tc.body)))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-grant-shape")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-manager-grant-shape")
if walletClient.lastGrantResource != nil {
t.Fatalf("client computed grant shape must not reach wallet-service: %+v", walletClient.lastGrantResource)
}
})
}
}
func TestGrantManagerResourceRejectsCrossCountryTarget(t *testing.T) {
walletClient := &fakeWalletClient{}
profileClient := &fakeUserProfileClient{
usersByID: map[int64]*userv1.User{
42: {UserId: 42, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "US"},
900001: {UserId: 900001, Status: userv1.UserStatus_USER_STATUS_ACTIVE, Country: "CA"},
},
}
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, profileClient)
body := []byte(`{"command_id":"mgr-grant-cross","target_user_id":"900001","resource_id":"101"}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/resource-grants", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-cross-country")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
assertEnvelope(t, recorder, http.StatusForbidden, httpkit.CodePermissionDenied, "req-manager-cross-country")
if walletClient.lastGrantResource != nil {
t.Fatalf("cross-country target must not reach wallet-service: %+v", walletClient.lastGrantResource)
}
}
func TestGrantManagerVIPLimitsToVipOneToThree(t *testing.T) {
walletClient := &fakeWalletClient{}
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
body := []byte(`{"command_id":"mgr-vip-4","target_user_id":"900001","level":4}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-vip4")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-manager-vip4")
if walletClient.lastGrantVip != nil {
t.Fatalf("VIP4 must not reach wallet-service: %+v", walletClient.lastGrantVip)
}
}
func TestGrantManagerVIPUsesManagerCenterGrantSource(t *testing.T) {
walletClient := &fakeWalletClient{}
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
body := []byte(`{"command_id":"mgr-vip-3","target_user_id":"900001","level":3}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-vip3")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if req := walletClient.lastGrantVip; req == nil || req.GetCommandId() != "mgr-vip-3" || req.GetTargetUserId() != 900001 || req.GetLevel() != 3 || req.GetGrantSource() != "manager_center" || req.GetOperatorUserId() != 42 {
t.Fatalf("wallet GrantVip request mismatch: %+v", req)
}
}
func TestManagerBlockAndUnblockCallUserService(t *testing.T) {
profileClient := &fakeUserProfileClient{}
router := newManagerCenterTestRouter(&fakeWalletClient{}, &fakeUserHostClient{}, profileClient)
body := []byte(`{"command_id":"block-163006","target_user_id":"900001","blocked_until_ms":1780000000000}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/blocks", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-block")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if req := profileClient.lastCreateBlock; req == nil || req.GetCommandId() != "block-163006" || req.GetManagerUserId() != 42 || req.GetTargetUserId() != 900001 || req.GetBlockedUntilMs() != 1780000000000 {
t.Fatalf("CreateManagerUserBlock request mismatch: %+v", req)
}
unblockReq := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/blocks/block-1/unblock", nil)
unblockReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
unblockReq.Header.Set("X-Request-ID", "req-manager-unblock")
unblockRecorder := httptest.NewRecorder()
router.ServeHTTP(unblockRecorder, unblockReq)
if unblockRecorder.Code != http.StatusOK {
t.Fatalf("unblock status mismatch: got %d body=%s", unblockRecorder.Code, unblockRecorder.Body.String())
}
if req := profileClient.lastUnblock; req == nil || req.GetManagerUserId() != 42 || req.GetBlockId() != "block-1" {
t.Fatalf("UnblockManagerUser request mismatch: %+v", req)
}
}
func TestSetManagerUserLevelCallsGrowthService(t *testing.T) {
growthClient := &fakeGrowthLevelClient{}
router := newManagerCenterTestRouterWithGrowth(&fakeWalletClient{}, &fakeUserHostClient{}, &fakeUserProfileClient{}, growthClient)
body := []byte(`{"command_id":"level-wealth-5","target_user_id":"900001","track":"wealth","level":5}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/levels", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-manager-level")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if req := growthClient.lastSet; req == nil || req.GetCommandId() != "level-wealth-5" || req.GetUserId() != 900001 || req.GetTrack() != "wealth" || req.GetLevel() != 5 || req.GetOperatorUserId() != 42 {
t.Fatalf("SetUserLevel request mismatch: %+v", req)
}
}
type fakeGrowthLevelClient struct {
lastSet *activityv1.SetUserLevelRequest
}
func (f *fakeGrowthLevelClient) GetMyLevelOverview(context.Context, *activityv1.GetMyLevelOverviewRequest) (*activityv1.GetMyLevelOverviewResponse, error) {
return &activityv1.GetMyLevelOverviewResponse{}, nil
}
func (f *fakeGrowthLevelClient) GetLevelTrack(context.Context, *activityv1.GetLevelTrackRequest) (*activityv1.GetLevelTrackResponse, error) {
return &activityv1.GetLevelTrackResponse{}, nil
}
func (f *fakeGrowthLevelClient) BatchGetUserLevelDisplayProfiles(context.Context, *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) {
return &activityv1.BatchGetUserLevelDisplayProfilesResponse{}, nil
}
func (f *fakeGrowthLevelClient) ListLevelRewards(context.Context, *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) {
return &activityv1.ListLevelRewardsResponse{}, nil
}
func (f *fakeGrowthLevelClient) SetUserLevel(_ context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
f.lastSet = req
return &activityv1.SetUserLevelResponse{EventId: "event-1", Status: "applied", Track: req.GetTrack(), NewLevel: req.GetLevel(), TotalValue: 1000}, nil
}

View File

@ -11,12 +11,14 @@ type Handler struct {
walletClient client.WalletClient
userHostClient client.UserHostClient
userProfileClient client.UserProfileClient
growthLevelClient client.GrowthLevelClient
}
type Config struct {
WalletClient client.WalletClient
UserHostClient client.UserHostClient
UserProfileClient client.UserProfileClient
GrowthLevelClient client.GrowthLevelClient
}
func New(config Config) *Handler {
@ -24,6 +26,7 @@ func New(config Config) *Handler {
walletClient: config.WalletClient,
userHostClient: config.UserHostClient,
userProfileClient: config.UserProfileClient,
growthLevelClient: config.GrowthLevelClient,
}
}
@ -32,5 +35,13 @@ func (h *Handler) ManagerHandlers() httproutes.ManagerHandlers {
ListManagerGrantResources: h.listManagerGrantResources,
GrantManagerResource: h.grantManagerResource,
LookupBusinessUsers: h.lookupBusinessUsers,
GetManagerOverview: h.getManagerOverview,
SearchManagerUsers: h.searchManagerUsers,
GrantManagerVIP: h.grantManagerVIP,
ManagerBlocks: h.managerBlocks,
ListManagerBlocks: h.listManagerBlocks,
CreateManagerBlock: h.createManagerBlock,
UnblockManagerBlock: h.unblockManagerBlock,
SetManagerUserLevel: h.setManagerUserLevel,
}
}

View File

@ -8,6 +8,7 @@ import (
"strconv"
"strings"
activityv1 "hyapp.local/api/proto/activity/v1"
userv1 "hyapp.local/api/proto/user/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
@ -15,7 +16,10 @@ import (
"hyapp/services/gateway-service/internal/auth"
)
const managerCenterCapability = "manager_center"
const managerResourceGrantCapability = "manager_resource_grant"
const managerGrantDayMS int64 = 24 * 60 * 60 * 1000
const managerGrantDefaultDurationMS int64 = 7 * managerGrantDayMS
type managerGrantResourceData struct {
ResourceID string `json:"resource_id"`
@ -39,6 +43,25 @@ type businessUserLookupData struct {
RegionName string `json:"region_name,omitempty"`
}
type managerUserData struct {
UserID string `json:"user_id"`
DisplayUserID string `json:"display_user_id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
Status string `json:"status"`
Country string `json:"country"`
RegionID int64 `json:"region_id,omitempty"`
RegionCode string `json:"region_code,omitempty"`
RegionName string `json:"region_name,omitempty"`
Levels map[string]managerLevelData `json:"levels"`
}
type managerLevelData struct {
Track string `json:"track"`
Level int32 `json:"level"`
TierID string `json:"tier_id,omitempty"`
}
type managerResourceGrantData struct {
GrantID string `json:"grant_id"`
CommandID string `json:"command_id"`
@ -50,11 +73,124 @@ type managerResourceGrantData struct {
CreatedAtMS int64 `json:"created_at_ms"`
}
func (h *Handler) listManagerGrantResources(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil || h.userHostClient == nil {
type managerBlockData struct {
BlockID string `json:"block_id"`
TargetUserID string `json:"target_user_id"`
TargetDisplayUserID string `json:"target_display_user_id"`
TargetUsername string `json:"target_username"`
TargetAvatar string `json:"target_avatar"`
Country string `json:"country"`
BlockedUntilMS int64 `json:"blocked_until_ms"`
Status string `json:"status"`
Reason string `json:"reason"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type managerLevelUpdateData struct {
EventID string `json:"event_id"`
Status string `json:"status"`
Track string `json:"track"`
PreviousLevel int32 `json:"previous_level"`
NewLevel int32 `json:"new_level"`
TotalValue int64 `json:"total_value"`
RewardJobCount int64 `json:"reward_job_count"`
ServerTimeMS int64 `json:"server_time_ms"`
}
func (h *Handler) getManagerOverview(writer http.ResponseWriter, request *http.Request) {
if h.userHostClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
// 经理中心首页只信 user-service 的角色事实;前端传 is_manager 或 identity 都不能绕过这里。
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return
}
httpkit.WriteOK(writer, request, map[string]any{
"is_manager": true,
"user_id": httpkit.UserIDString(actor.GetUserId()),
"display_user_id": actor.GetDisplayUserId(),
"username": actor.GetUsername(),
"avatar": actor.GetAvatar(),
"country": actor.GetCountry(),
"region_id": actor.GetRegionId(),
"region_code": actor.GetRegionCode(),
"region_name": actor.GetRegionName(),
})
}
func (h *Handler) searchManagerUsers(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.userHostClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
// 搜索先走已有 business lookup 保持短号/昵称解析一致,再用 BatchGetUsers 拿国家和等级投影做最终过滤。
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return
}
pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
if pageSize > 20 {
pageSize = 20
}
lookupResp, err := h.userProfileClient.BusinessUserLookup(request.Context(), &userv1.BusinessUserLookupRequest{
Meta: httpkit.UserMeta(request, ""),
ActorUserId: actor.GetUserId(),
Scene: "manager_resource_grant",
Keyword: strings.TrimSpace(request.URL.Query().Get("keyword")),
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
userIDs := make([]int64, 0, len(lookupResp.GetUsers()))
seen := map[int64]bool{}
for _, item := range lookupResp.GetUsers() {
if id := item.GetUserId(); id > 0 && !seen[id] {
seen[id] = true
userIDs = append(userIDs, id)
}
}
if len(userIDs) == 0 {
httpkit.WriteOK(writer, request, map[string]any{"items": []managerUserData{}, "total": 0, "page_size": pageSize})
return
}
usersResp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{
Meta: httpkit.UserMeta(request, ""),
UserIds: userIDs,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
levels := h.managerLevelsByUser(request, userIDs)
items := make([]managerUserData, 0, len(userIDs))
for _, userID := range userIDs {
user := usersResp.GetUsers()[userID]
// 同国家限制必须放在 gateway 写入口和读入口都做,避免 H5 通过短号枚举跨国家用户。
if user == nil || user.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE || !sameCountry(actor.GetCountry(), user.GetCountry()) {
continue
}
items = append(items, managerUserFromProto(user, levels[userID]))
}
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items), "page_size": pageSize})
}
func (h *Handler) listManagerGrantResources(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil || h.userHostClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
if _, ok := h.requireManagerCenterActor(writer, request); !ok {
return
}
if !h.requireManagerResourceGrantCapability(writer, request) {
return
}
@ -134,7 +270,12 @@ func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http
if !httpkit.Decode(writer, request, &body) {
return
}
if body.Quantity != nil || body.DurationMS != nil || body.DurationMSAlt != nil {
if body.Quantity != nil {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
durationMS, durationOK := managerGrantDurationMS(body.DurationMS, body.DurationMSAlt)
if !durationOK {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
@ -151,16 +292,8 @@ func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: targetUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if target := targetResp.GetUser(); target == nil || target.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
// 赠送资源前先确认目标用户仍然 active 且与经理同国家wallet-service 还会在事务内复验资源白名单。
if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok {
return
}
reason := strings.TrimSpace(body.Reason)
@ -169,12 +302,13 @@ func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http
}
actorUserID := auth.UserIDFromContext(request.Context())
resp, err := h.walletClient.GrantResource(request.Context(), &walletv1.GrantResourceRequest{
CommandId: commandID,
AppCode: appcode.FromContext(request.Context()),
TargetUserId: targetUserID,
ResourceId: resourceID,
Quantity: 1,
DurationMs: 0,
CommandId: commandID,
AppCode: appcode.FromContext(request.Context()),
TargetUserId: targetUserID,
ResourceId: resourceID,
Quantity: 1,
// 经理中心只能赠送产品允许的三个限时档位;数量、来源和操作人仍由网关固定,避免 H5 自行组装账务形状。
DurationMs: durationMS,
Reason: reason,
OperatorUserId: actorUserID,
GrantSource: "manager_center",
@ -199,6 +333,242 @@ func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http
httpkit.WriteOK(writer, request, grant)
}
func (h *Handler) grantManagerVIP(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil || h.userHostClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
TargetUserID json.RawMessage `json:"target_user_id"`
TargetUserIDAlt json.RawMessage `json:"targetUserId"`
Level int32 `json:"level"`
Reason string `json:"reason"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = strings.TrimSpace(body.CommandIDAlt)
}
targetUserID, targetOK := rawInt64(body.TargetUserID, body.TargetUserIDAlt)
if commandID == "" || !targetOK || targetUserID <= 0 || body.Level < 1 || body.Level > 3 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
// 经理中心复用后台 VIP 发放账务能力,但 source 固定为 manager_center钱包侧据此保留统一 IM/outbox 行为。
if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok {
return
}
reason := strings.TrimSpace(body.Reason)
if reason == "" {
reason = "manager_center"
}
resp, err := h.walletClient.GrantVip(request.Context(), &walletv1.GrantVipRequest{
CommandId: commandID,
AppCode: appcode.FromContext(request.Context()),
TargetUserId: targetUserID,
Level: body.Level,
GrantSource: "manager_center",
OperatorUserId: auth.UserIDFromContext(request.Context()),
Reason: reason,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{
"transaction_id": resp.GetTransactionId(),
"level": resp.GetVip().GetLevel(),
"active": resp.GetVip().GetActive(),
"expires_at_ms": resp.GetVip().GetExpiresAtMs(),
"server_time_ms": resp.GetServerTimeMs(),
})
}
func (h *Handler) managerBlocks(writer http.ResponseWriter, request *http.Request) {
switch request.Method {
case http.MethodGet:
h.listManagerBlocks(writer, request)
case http.MethodPost:
h.createManagerBlock(writer, request)
default:
httpkit.WriteError(writer, request, http.StatusMethodNotAllowed, httpkit.CodeInvalidArgument, "method not allowed")
}
}
func (h *Handler) listManagerBlocks(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.userHostClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return
}
pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 50)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
if pageSize > 100 {
pageSize = 100
}
status := strings.TrimSpace(request.URL.Query().Get("status"))
resp, err := h.userProfileClient.ListManagerUserBlocks(request.Context(), &userv1.ListManagerUserBlocksRequest{
Meta: httpkit.UserMeta(request, ""),
ManagerUserId: actor.GetUserId(),
Status: status,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
items := make([]managerBlockData, 0, len(resp.GetBlocks()))
for _, block := range resp.GetBlocks() {
items = append(items, managerBlockFromProto(block))
}
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items), "page_size": pageSize})
}
func (h *Handler) createManagerBlock(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.userHostClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
TargetUserID json.RawMessage `json:"target_user_id"`
TargetUserIDAlt json.RawMessage `json:"targetUserId"`
BlockedUntilMS int64 `json:"blocked_until_ms"`
BlockedUntilMSAlt int64 `json:"blockedUntilMs"`
Reason string `json:"reason"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = strings.TrimSpace(body.CommandIDAlt)
}
targetUserID, targetOK := rawInt64(body.TargetUserID, body.TargetUserIDAlt)
blockedUntilMS := body.BlockedUntilMS
if blockedUntilMS <= 0 {
blockedUntilMS = body.BlockedUntilMSAlt
}
if commandID == "" || !targetOK || targetUserID <= 0 || blockedUntilMS <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
// user-service 会再次读取双方用户并校验同国家,这里保留 gateway 早拒绝以减少无效封禁事务。
if _, ok := h.requireSameCountryTargetWithActor(writer, request, actor, targetUserID); !ok {
return
}
resp, err := h.userProfileClient.CreateManagerUserBlock(request.Context(), &userv1.CreateManagerUserBlockRequest{
Meta: httpkit.UserMeta(request, ""),
CommandId: commandID,
ManagerUserId: actor.GetUserId(),
TargetUserId: targetUserID,
BlockedUntilMs: blockedUntilMS,
Reason: strings.TrimSpace(body.Reason),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, managerBlockFromProto(resp.GetBlock()))
}
func (h *Handler) unblockManagerBlock(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.userHostClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return
}
var body struct {
Reason string `json:"reason"`
}
if request.Body != nil && request.ContentLength != 0 {
if !httpkit.Decode(writer, request, &body) {
return
}
}
blockID := strings.TrimSpace(request.PathValue("block_id"))
if blockID == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.userProfileClient.UnblockManagerUser(request.Context(), &userv1.UnblockManagerUserRequest{
Meta: httpkit.UserMeta(request, ""),
ManagerUserId: actor.GetUserId(),
BlockId: blockID,
Reason: strings.TrimSpace(body.Reason),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, managerBlockFromProto(resp.GetBlock()))
}
func (h *Handler) setManagerUserLevel(writer http.ResponseWriter, request *http.Request) {
if h.growthLevelClient == nil || h.userProfileClient == nil || h.userHostClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
TargetUserID json.RawMessage `json:"target_user_id"`
TargetUserIDAlt json.RawMessage `json:"targetUserId"`
Track string `json:"track"`
Level int32 `json:"level"`
Reason string `json:"reason"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = strings.TrimSpace(body.CommandIDAlt)
}
targetUserID, targetOK := rawInt64(body.TargetUserID, body.TargetUserIDAlt)
track := strings.ToLower(strings.TrimSpace(body.Track))
if commandID == "" || !targetOK || targetUserID <= 0 || !validManagerLevelTrack(track) || body.Level < 1 || body.Level > 11 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
// 等级设置会把目标轨道 total_value 直接改到对应规则阈值,并由 activity-service 补齐 1..目标等级奖励。
if _, ok := h.requireSameCountryTarget(writer, request, targetUserID); !ok {
return
}
resp, err := h.growthLevelClient.SetUserLevel(request.Context(), &activityv1.SetUserLevelRequest{
Meta: httpkit.ActivityMeta(request),
CommandId: commandID,
UserId: targetUserID,
Track: track,
Level: body.Level,
OperatorUserId: auth.UserIDFromContext(request.Context()),
Reason: strings.TrimSpace(body.Reason),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, managerLevelUpdateFromProto(resp))
}
func (h *Handler) requireManagerResourceGrantCapability(writer http.ResponseWriter, request *http.Request) bool {
resp, err := h.userHostClient.CheckBusinessCapability(request.Context(), &userv1.CheckBusinessCapabilityRequest{
Meta: httpkit.UserMeta(request, ""),
@ -216,6 +586,76 @@ func (h *Handler) requireManagerResourceGrantCapability(writer http.ResponseWrit
return true
}
func (h *Handler) requireManagerCenterActor(writer http.ResponseWriter, request *http.Request) (*userv1.User, bool) {
if !h.requireManagerCapability(writer, request, managerCenterCapability) {
return nil, false
}
resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return nil, false
}
actor := resp.GetUser()
if actor == nil || actor.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE {
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
return nil, false
}
return actor, true
}
func (h *Handler) requireManagerCapability(writer http.ResponseWriter, request *http.Request, capability string) bool {
resp, err := h.userHostClient.CheckBusinessCapability(request.Context(), &userv1.CheckBusinessCapabilityRequest{
Meta: httpkit.UserMeta(request, ""),
ActorUserId: auth.UserIDFromContext(request.Context()),
Capability: capability,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return false
}
if !resp.GetAllowed() {
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
return false
}
return true
}
func (h *Handler) requireSameCountryTarget(writer http.ResponseWriter, request *http.Request, targetUserID int64) (*userv1.User, bool) {
actor, ok := h.requireManagerCenterActor(writer, request)
if !ok {
return nil, false
}
return h.requireSameCountryTargetWithActor(writer, request, actor, targetUserID)
}
func (h *Handler) requireSameCountryTargetWithActor(writer http.ResponseWriter, request *http.Request, actor *userv1.User, targetUserID int64) (*userv1.User, bool) {
targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: targetUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return nil, false
}
target := targetResp.GetUser()
if target == nil || target.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return nil, false
}
if !sameCountry(actor.GetCountry(), target.GetCountry()) {
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
return nil, false
}
return target, true
}
func sameCountry(left string, right string) bool {
return strings.EqualFold(strings.TrimSpace(left), strings.TrimSpace(right)) && strings.TrimSpace(left) != ""
}
func managerGrantResourceFromProto(resource *walletv1.Resource) managerGrantResourceData {
if resource == nil {
return managerGrantResourceData{}
@ -232,6 +672,140 @@ func managerGrantResourceFromProto(resource *walletv1.Resource) managerGrantReso
}
}
func managerUserFromProto(user *userv1.User, levels map[string]managerLevelData) managerUserData {
if user == nil {
return managerUserData{Levels: managerDefaultLevels()}
}
if levels == nil {
levels = managerDefaultLevels()
}
return managerUserData{
UserID: httpkit.UserIDString(user.GetUserId()),
DisplayUserID: user.GetDisplayUserId(),
Username: user.GetUsername(),
Avatar: user.GetAvatar(),
Status: userStatusString(user.GetStatus()),
Country: user.GetCountry(),
RegionID: user.GetRegionId(),
RegionCode: user.GetRegionCode(),
RegionName: user.GetRegionName(),
Levels: levels,
}
}
func (h *Handler) managerLevelsByUser(request *http.Request, userIDs []int64) map[int64]map[string]managerLevelData {
result := map[int64]map[string]managerLevelData{}
for _, userID := range userIDs {
result[userID] = managerDefaultLevels()
}
if h.growthLevelClient == nil || len(userIDs) == 0 {
return result
}
resp, err := h.growthLevelClient.BatchGetUserLevelDisplayProfiles(request.Context(), &activityv1.BatchGetUserLevelDisplayProfilesRequest{
Meta: httpkit.ActivityMeta(request),
UserIds: userIDs,
})
if err != nil {
return result
}
for _, profile := range resp.GetProfiles() {
if profile.GetUserId() <= 0 {
continue
}
result[profile.GetUserId()] = map[string]managerLevelData{
"wealth": managerLevelFromProto(profile.GetWealth()),
"charm": managerLevelFromProto(profile.GetCharm()),
"game": managerLevelFromProto(profile.GetGame()),
}
}
return result
}
func managerDefaultLevels() map[string]managerLevelData {
return map[string]managerLevelData{
"wealth": {Track: "wealth"},
"charm": {Track: "charm"},
"game": {Track: "game"},
}
}
func managerLevelFromProto(track *activityv1.LevelDisplayTrackProfile) managerLevelData {
if track == nil {
return managerLevelData{}
}
return managerLevelData{
Track: track.GetTrack(),
Level: track.GetLevel(),
TierID: httpkit.UserIDString(track.GetTierId()),
}
}
func managerBlockFromProto(block *userv1.ManagerUserBlock) managerBlockData {
if block == nil {
return managerBlockData{}
}
return managerBlockData{
BlockID: block.GetBlockId(),
TargetUserID: httpkit.UserIDString(block.GetTargetUserId()),
TargetDisplayUserID: block.GetTargetDisplayUserId(),
TargetUsername: block.GetTargetUsername(),
TargetAvatar: block.GetTargetAvatar(),
Country: block.GetCountry(),
BlockedUntilMS: block.GetBlockedUntilMs(),
Status: block.GetStatus(),
Reason: block.GetReason(),
CreatedAtMS: block.GetCreatedAtMs(),
UpdatedAtMS: block.GetUpdatedAtMs(),
}
}
func managerLevelUpdateFromProto(resp *activityv1.SetUserLevelResponse) managerLevelUpdateData {
if resp == nil {
return managerLevelUpdateData{}
}
return managerLevelUpdateData{
EventID: resp.GetEventId(),
Status: resp.GetStatus(),
Track: resp.GetTrack(),
PreviousLevel: resp.GetPreviousLevel(),
NewLevel: resp.GetNewLevel(),
TotalValue: resp.GetTotalValue(),
RewardJobCount: resp.GetRewardJobCount(),
ServerTimeMS: resp.GetServerTimeMs(),
}
}
func validManagerLevelTrack(track string) bool {
switch track {
case "wealth", "charm", "game":
return true
default:
return false
}
}
func managerGrantDurationMS(primary *int64, alternate *int64) (int64, bool) {
// 旧 H5 还没有 duration 字段,默认 7 天保证入口兼容;新 H5 只能在 7/30/90 天之间选择。
if primary == nil && alternate == nil {
return managerGrantDefaultDurationMS, true
}
if primary != nil && alternate != nil && *primary != *alternate {
return 0, false
}
value := int64(0)
if primary != nil {
value = *primary
} else {
value = *alternate
}
switch value {
case 7 * managerGrantDayMS, 30 * managerGrantDayMS, 90 * managerGrantDayMS:
return value, true
default:
return 0, false
}
}
func businessUserLookupFromProto(user *userv1.BusinessUserLookupItem) businessUserLookupData {
if user == nil {
return businessUserLookupData{}

View File

@ -311,13 +311,20 @@ type fakeUserProfileClient struct {
lastComplete *userv1.CompleteOnboardingRequest
lastUpdate *userv1.UpdateUserProfileRequest
lastCountry *userv1.ChangeUserCountryRequest
lastCreateBlock *userv1.CreateManagerUserBlockRequest
lastListBlocks *userv1.ListManagerUserBlocksRequest
lastUnblock *userv1.UnblockManagerUserRequest
getErr error
statsResp *userv1.GetMyProfileStatsResponse
businessLookupResp *userv1.BusinessUserLookupResponse
businessLookupErr error
blocksResp *userv1.ListManagerUserBlocksResponse
createBlockResp *userv1.CreateManagerUserBlockResponse
unblockResp *userv1.UnblockManagerUserResponse
statsErr error
completeErr error
countryErr error
blockErr error
regionID int64
regionByUserID map[int64]int64
usersByID map[int64]*userv1.User
@ -389,6 +396,12 @@ type fakeUserCountryQueryClient struct {
blockedErr error
}
type fakeUserRegionClient struct {
last *userv1.GetRegionRequest
resp *userv1.RegionResponse
err error
}
type fakeUserHostClient struct {
lastSearchAgencies *userv1.SearchAgenciesRequest
searchAgenciesResp *userv1.SearchAgenciesResponse
@ -481,6 +494,8 @@ type fakeWalletClient struct {
vipErr error
lastPurchaseVip *walletv1.PurchaseVipRequest
purchaseVipResp *walletv1.PurchaseVipResponse
lastGrantVip *walletv1.GrantVipRequest
grantVipResp *walletv1.GrantVipResponse
lastTransfer *walletv1.TransferCoinFromSellerRequest
transferResp *walletv1.TransferCoinFromSellerResponse
transferErr error
@ -720,6 +735,7 @@ func (f *fakeUserProfileClient) BatchGetUsers(_ context.Context, req *userv1.Bat
}
users[userID] = &userv1.User{
UserId: userID,
Status: userv1.UserStatus_USER_STATUS_ACTIVE,
DisplayUserId: strconv.FormatInt(100000+userID, 10),
Username: "user-" + strconv.FormatInt(userID, 10),
Gender: "female",
@ -794,6 +810,53 @@ func (f *fakeUserProfileClient) ChangeUserCountry(_ context.Context, req *userv1
}, NextChangeAllowedAtMs: 3600000, OldRegionId: 1001, NewRegionId: 2002, RegionChanged: true}, nil
}
func (f *fakeUserProfileClient) CreateManagerUserBlock(_ context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
f.lastCreateBlock = req
if f.blockErr != nil {
return nil, f.blockErr
}
if f.createBlockResp != nil {
return f.createBlockResp, nil
}
return &userv1.CreateManagerUserBlockResponse{Block: &userv1.ManagerUserBlock{
BlockId: "block-1",
ManagerUserId: req.GetManagerUserId(),
TargetUserId: req.GetTargetUserId(),
TargetDisplayUserId: "163006",
TargetUsername: "blocked",
Country: "US",
BlockedUntilMs: req.GetBlockedUntilMs(),
Status: "active",
Reason: req.GetReason(),
}}, nil
}
func (f *fakeUserProfileClient) ListManagerUserBlocks(_ context.Context, req *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error) {
f.lastListBlocks = req
if f.blockErr != nil {
return nil, f.blockErr
}
if f.blocksResp != nil {
return f.blocksResp, nil
}
return &userv1.ListManagerUserBlocksResponse{}, nil
}
func (f *fakeUserProfileClient) UnblockManagerUser(_ context.Context, req *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error) {
f.lastUnblock = req
if f.blockErr != nil {
return nil, f.blockErr
}
if f.unblockResp != nil {
return f.unblockResp, nil
}
return &userv1.UnblockManagerUserResponse{Block: &userv1.ManagerUserBlock{
BlockId: req.GetBlockId(),
ManagerUserId: req.GetManagerUserId(),
Status: "released",
}}, nil
}
func (f *fakeUserIdentityClient) GetUserIdentity(_ context.Context, req *userv1.GetUserIdentityRequest) (*userv1.GetUserIdentityResponse, error) {
return &userv1.GetUserIdentityResponse{Identity: &userv1.UserIdentity{
UserId: req.GetUserId(),
@ -984,6 +1047,18 @@ func (f *fakeUserCountryQueryClient) ListLoginRiskBlockedCountries(_ context.Con
return &userv1.ListLoginRiskBlockedCountriesResponse{}, nil
}
func (f *fakeUserRegionClient) GetRegion(_ context.Context, req *userv1.GetRegionRequest) (*userv1.RegionResponse, error) {
f.last = req
if f.err != nil {
return nil, f.err
}
if f.resp != nil {
return f.resp, nil
}
return &userv1.RegionResponse{Region: &userv1.Region{RegionId: req.GetRegionId(), Countries: []string{"US"}}}, nil
}
func (f *fakeMessageInboxClient) ListMessageTabs(_ context.Context, req *activityv1.ListMessageTabsRequest) (*activityv1.ListMessageTabsResponse, error) {
f.lastTabs = req
if f.err != nil {
@ -1511,6 +1586,25 @@ func (f *fakeWalletClient) PurchaseVip(_ context.Context, req *walletv1.Purchase
return &walletv1.PurchaseVipResponse{}, nil
}
func (f *fakeWalletClient) DebitCPBreakupFee(context.Context, *walletv1.DebitCPBreakupFeeRequest) (*walletv1.DebitCPBreakupFeeResponse, error) {
return &walletv1.DebitCPBreakupFeeResponse{}, nil
}
func (f *fakeWalletClient) GrantVip(_ context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
f.lastGrantVip = req
if f.vipErr != nil {
return nil, f.vipErr
}
if f.grantVipResp != nil {
return f.grantVipResp, nil
}
return &walletv1.GrantVipResponse{
TransactionId: "vip-grant-tx",
Vip: &walletv1.UserVip{UserId: req.GetTargetUserId(), Level: req.GetLevel(), Active: true},
ServerTimeMs: 1_780_000_000_000,
}, nil
}
func (f *fakeWalletClient) TransferCoinFromSeller(_ context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error) {
f.lastTransfer = req
if f.transferErr != nil {
@ -1816,6 +1910,9 @@ func TestRoutesWriteUnifiedEnvelopeAndGenerateRequestID(t *testing.T) {
if profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 42 || client.lastCreate.GetVisibleRegionId() != 1001 {
t.Fatalf("CreateRoom must derive visible_region_id from user-service: profile=%+v create=%+v", profileClient.lastGet, client.lastCreate)
}
if client.lastCreate.GetOwnerCountryCode() != "US" {
t.Fatalf("CreateRoom must derive owner_country_code from user-service: %+v", client.lastCreate)
}
if client.lastCreate.GetRoomName() != "Lobby" || client.lastCreate.GetRoomAvatar() != "https://cdn.example.com/room.png" || client.lastCreate.GetRoomDescription() != "welcome" {
t.Fatalf("CreateRoom should trim and forward room profile fields: %+v", client.lastCreate)
}
@ -2310,16 +2407,29 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T)
func TestListRoomsUsesUserRegionFromUserService(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001}
regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{
RegionId: 1001,
Countries: []string{"AE", "US", "TR"},
}}}
countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{
Countries: []*userv1.Country{
{CountryId: 1, CountryCode: "AE", CountryName: "United Arab Emirates", CountryDisplayName: "UAE", Flag: "🇦🇪"},
{CountryId: 2, CountryCode: "US", CountryName: "United States", CountryDisplayName: "US", Flag: "🇺🇸"},
{CountryId: 3, CountryCode: "TR", CountryName: "Türkiye", CountryDisplayName: "TR", Flag: "🇹🇷"},
},
}}
queryClient := &fakeRoomQueryClient{resp: &roomv1.ListRoomsResponse{
Rooms: []*roomv1.RoomListItem{
{RoomId: "room-1", VisibleRegionId: 1001, Heat: 90, Locked: true},
{RoomId: "room-1", VisibleRegionId: 1001, Heat: 90, Locked: true, CountryCode: "US"},
},
NextCursor: "cursor-2",
}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
handler.SetRoomQueryClient(queryClient)
handler.SetUserRegionClient(regionClient)
handler.SetUserCountryQueryClient(countryClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?tab=hot&limit=2&cursor=cursor-1&q=room&visible_region_id=9999", nil)
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?tab=hot&limit=2&cursor=cursor-1&q=room&country=US&visible_region_id=9999", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-room-list")
recorder := httptest.NewRecorder()
@ -2338,21 +2448,177 @@ func TestListRoomsUsesUserRegionFromUserService(t *testing.T) {
if queryClient.lastList.GetViewerUserId() != 42 || queryClient.lastList.GetVisibleRegionId() != 1001 {
t.Fatalf("ListRooms must use authenticated user and server-side region: %+v", queryClient.lastList)
}
if queryClient.lastList.GetTab() != "hot" || queryClient.lastList.GetLimit() != 2 || queryClient.lastList.GetCursor() != "cursor-1" || queryClient.lastList.GetQuery() != "room" {
if queryClient.lastList.GetTab() != "hot" || queryClient.lastList.GetLimit() != 2 || queryClient.lastList.GetCursor() != "cursor-1" || queryClient.lastList.GetQuery() != "room" || queryClient.lastList.GetCountryCode() != "US" {
t.Fatalf("ListRooms query params were not propagated: %+v", queryClient.lastList)
}
if regionClient.last == nil || regionClient.last.GetRegionId() != 1001 || countryClient.last == nil {
t.Fatalf("ListRooms must fetch region countries and country metadata: region=%+v countries=%+v", regionClient.last, countryClient.last)
}
var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode room list response failed: %v", err)
}
data := response.Data.(map[string]any)
countries := data["countries"].([]any)
if len(countries) != 3 {
t.Fatalf("room list must expose current region countries: %+v", countries)
}
for index, want := range []string{"US", "AE", "TR"} {
country := countries[index].(map[string]any)
if country["country_code"] != want {
t.Fatalf("country order mismatch at %d: want %s got %+v", index, want, countries)
}
}
rooms := data["rooms"].([]any)
first := rooms[0].(map[string]any)
if first["im_group_id"] != "room-1" || first["locked"] != true {
if first["im_group_id"] != "room-1" || first["locked"] != true || first["country_code"] != "US" {
t.Fatalf("room list must expose room IM group id: %+v", first)
}
}
func TestGetRoomRocketEmbedsRewardResourceGroups(t *testing.T) {
queryClient := &fakeRoomQueryClient{rocketResp: &roomv1.GetRoomRocketResponse{
Rocket: &roomv1.RoomRocketInfo{
Enabled: true,
LaunchDelayMs: 1200,
Levels: []*roomv1.RoomRocketLevel{{
Level: 1,
FuelThreshold: 100,
InRoomRewards: []*roomv1.RoomRocketRewardItem{{
RewardItemId: "in-room-coin",
ResourceGroupId: 23,
Weight: 100,
DisplayName: "1888 Coins",
}},
}},
State: &roomv1.RoomRocketState{
CurrentLevel: 1,
CurrentFuel: 0,
FuelThreshold: 100,
Status: "charging",
LastRewards: []*roomv1.RoomRocketRewardGrant{{
RewardRole: "igniter",
UserId: 42,
RewardItemId: "chat-bubble",
ResourceGroupId: 24,
DisplayName: "Blue Bubble",
Status: "succeeded",
}},
},
},
ServerTimeMs: 1_780_000_000_000,
}}
walletClient := &fakeWalletClient{resourceGroupsByID: map[int64]*walletv1.ResourceGroup{
23: {
GroupId: 23,
GroupCode: "coin_1888",
Name: "Coin Reward",
Status: "active",
Items: []*walletv1.ResourceGroupItem{{
GroupItemId: 2301,
ItemType: "wallet_asset",
WalletAssetType: "COIN",
WalletAssetAmount: 1888,
Quantity: 1,
}},
},
24: {
GroupId: 24,
GroupCode: "bubble_7d",
Name: "Chat Bubble",
Status: "active",
Items: []*walletv1.ResourceGroupItem{{
GroupItemId: 2401,
ItemType: "resource",
ResourceId: 8101,
Resource: &walletv1.Resource{
ResourceId: 8101,
ResourceCode: "bubble_blue",
ResourceType: "chat_bubble",
Name: "Blue Bubble",
Status: "active",
AssetUrl: "https://cdn.example/bubble.png",
},
Quantity: 1,
DurationMs: 604800000,
}},
},
}}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetRoomQueryClient(queryClient)
handler.SetWalletClient(walletClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/room_1001/rocket", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
request.Header.Set("X-Request-ID", "req-room-rocket")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if queryClient.lastRocket == nil || queryClient.lastRocket.GetViewerUserId() != 42 || queryClient.lastRocket.GetRoomId() != "room_1001" {
t.Fatalf("GetRoomRocket must forward authenticated viewer and room: %+v", queryClient.lastRocket)
}
var envelope httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
t.Fatalf("decode rocket response failed: %v", err)
}
data := envelope.Data.(map[string]any)
level := data["levels"].([]any)[0].(map[string]any)
inRoomReward := level["in_room_rewards"].([]any)[0].(map[string]any)
inRoomGroup := inRoomReward["resource_group"].(map[string]any)
if inRoomReward["resource_group_id"].(float64) != 23 || inRoomGroup["group_id"].(float64) != 23 {
t.Fatalf("rocket reward must embed resource group id 23: %+v", inRoomReward)
}
coinItem := inRoomGroup["items"].([]any)[0].(map[string]any)
if coinItem["item_type"] != "wallet_asset" || coinItem["wallet_asset_type"] != "COIN" || coinItem["wallet_asset_amount"].(float64) != 1888 {
t.Fatalf("rocket reward group must expose wallet asset amount: %+v", coinItem)
}
state := data["state"].(map[string]any)
lastReward := state["last_rewards"].([]any)[0].(map[string]any)
lastGroup := lastReward["resource_group"].(map[string]any)
resourceItem := lastGroup["items"].([]any)[0].(map[string]any)
resource := resourceItem["resource"].(map[string]any)
if lastReward["resource_group_id"].(float64) != 24 || resource["resource_type"] != "chat_bubble" || resource["asset_url"] != "https://cdn.example/bubble.png" || resourceItem["duration_ms"].(float64) != 604800000 {
t.Fatalf("rocket last reward must expose resource material and duration: reward=%+v item=%+v", lastReward, resourceItem)
}
}
func TestListRoomsRejectsCountryOutsideUserRegion(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001}
regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{
RegionId: 1001,
Countries: []string{"US", "AE"},
}}}
countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{
Countries: []*userv1.Country{
{CountryId: 1, CountryCode: "US", Flag: "🇺🇸"},
{CountryId: 2, CountryCode: "AE", Flag: "🇦🇪"},
{CountryId: 3, CountryCode: "CN", Flag: "🇨🇳"},
},
}}
queryClient := &fakeRoomQueryClient{}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
handler.SetRoomQueryClient(queryClient)
handler.SetUserRegionClient(regionClient)
handler.SetUserCountryQueryClient(countryClient)
router := handler.Routes(auth.NewVerifier("secret"))
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms?tab=hot&limit=2&country=CN", nil)
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
if queryClient.lastList != nil {
t.Fatalf("invalid country must not reach room-service: %+v", queryClient.lastList)
}
}
func TestSetRoomPasswordForwardsPasswordAndReturnsLockedState(t *testing.T) {
client := &fakeRoomClient{}
router := NewHandlerWithClients(client, nil, nil, &fakeUserProfileClient{}).Routes(auth.NewVerifier("secret"))
@ -4714,16 +4980,18 @@ func TestAchievementAndBadgeRoutesEmbedResourceMaterials(t *testing.T) {
func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) {
rewardResource := &walletv1.Resource{
ResourceId: 8101,
ResourceCode: "weekly_star_frame",
ResourceType: "avatar_frame",
Name: "Weekly Star Frame",
Status: "active",
Grantable: true,
GrantStrategy: "new_entitlement",
UsageScopes: []string{"profile"},
AssetUrl: "https://cdn.example/weekly-star-frame.png",
SortOrder: 10,
ResourceId: 8101,
ResourceCode: "weekly_star_frame",
ResourceType: "avatar_frame",
Name: "Weekly Star Frame",
Status: "active",
Grantable: true,
GrantStrategy: "new_entitlement",
WalletAssetType: "FRAME",
UsageScopes: []string{"profile"},
AssetUrl: "https://cdn.example/weekly-frame.png",
PreviewUrl: "https://cdn.example/weekly-frame-preview.png",
SortOrder: 10,
}
weeklyStarClient := &fakeWeeklyStarClient{
currentResp: &activityv1.GetWeeklyStarCurrentResponse{
@ -4733,33 +5001,47 @@ func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) {
RegionId: 686,
Title: "Weekly Star",
Status: "active",
StartMs: 1778256000000,
EndMs: 1778860800000,
Gifts: []*activityv1.WeeklyStarGift{{GiftId: "308", SortOrder: 1}},
Rewards: []*activityv1.WeeklyStarReward{{RankNo: 1, ResourceGroupId: 23}},
StartMs: 1780848000000,
EndMs: 1781452740000,
Gifts: []*activityv1.WeeklyStarGift{{
GiftId: "308",
SortOrder: 1,
}},
Rewards: []*activityv1.WeeklyStarReward{{
RankNo: 1,
ResourceGroupId: 23,
}},
},
TopEntries: []*activityv1.WeeklyStarLeaderboardEntry{{RankNo: 1, UserId: 88, Score: 120000, FirstScoredAtMs: 1778256001000, LastScoredAtMs: 1778256002000}},
ServerTimeMs: 1778256003000,
TopEntries: []*activityv1.WeeklyStarLeaderboardEntry{{
RankNo: 1,
UserId: 88,
Score: 120000,
FirstScoredAtMs: 1780849000000,
LastScoredAtMs: 1780849900000,
}},
ServerTimeMs: 1780916324006,
},
}
walletClient := &fakeWalletClient{
resourceGroupsByID: map[int64]*walletv1.ResourceGroup{23: {
GroupId: 23,
GroupCode: "weekly_star_top1",
Name: "Top 1 Resource Group",
Status: "active",
GroupId: 23,
GroupCode: "weekly_star_top1",
Name: "Top 1 Resource Group",
Status: "active",
Description: "Top 1 reward materials",
Items: []*walletv1.ResourceGroupItem{{
GroupItemId: 101,
GroupItemId: 501,
ItemType: "resource",
ResourceId: rewardResource.GetResourceId(),
ResourceId: 8101,
Resource: rewardResource,
Quantity: 1,
DurationMs: 7 * 24 * int64(time.Hour/time.Millisecond),
SortOrder: 10,
SortOrder: 1,
}},
}},
}
userClient := &fakeUserProfileClient{
regionID: 686,
usersByID: map[int64]*userv1.User{
42: {UserId: 42, DisplayUserId: "163000", Username: "viewer", RegionId: 686},
88: {UserId: 88, DisplayUserId: "163088", Username: "weekly_ranker", Avatar: "https://cdn.example/ranker.png", RegionId: 686},
@ -4775,27 +5057,28 @@ func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("weekly star status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
t.Fatalf("weekly star current status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
}
var envelope struct {
Code string `json:"code"`
Data struct {
Cycle struct {
Rewards []struct {
RankNo int32 `json:"rank_no"`
ResourceGroupID int64 `json:"resource_group_id"`
ResourceGroup struct {
GroupID int64 `json:"group_id"`
Name string `json:"name"`
Items []struct {
ResourceID int64 `json:"resource_id"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
Resource struct {
ResourceID int64 `json:"resource_id"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
AssetURL string `json:"asset_url"`
} `json:"resource"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
} `json:"items"`
} `json:"resource_group"`
} `json:"rewards"`
@ -4808,10 +5091,10 @@ func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) {
} `json:"data"`
}
if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
t.Fatalf("decode weekly star response failed: %v", err)
t.Fatalf("decode weekly star current response failed: %v", err)
}
if weeklyStarClient.lastCurrent == nil || weeklyStarClient.lastCurrent.GetUserId() != 42 || weeklyStarClient.lastCurrent.GetRegionId() != 686 {
t.Fatalf("weekly star request mismatch: %+v", weeklyStarClient.lastCurrent)
t.Fatalf("weekly star current request mismatch: %+v", weeklyStarClient.lastCurrent)
}
if envelope.Code != httpkit.CodeOK || len(envelope.Data.Cycle.Rewards) != 1 {
t.Fatalf("weekly star response missing rewards: %+v", envelope)

View File

@ -15,6 +15,8 @@ type Handler struct {
roomGuardClient client.RoomGuardClient
roomQueryClient client.RoomQueryClient
userProfileClient client.UserProfileClient
userCountryClient client.UserCountryQueryClient
userRegionClient client.UserRegionClient
userSocialClient client.UserSocialClient
userHostClient client.UserHostClient
walletClient client.WalletClient
@ -28,6 +30,8 @@ type Config struct {
RoomGuardClient client.RoomGuardClient
RoomQueryClient client.RoomQueryClient
UserProfileClient client.UserProfileClient
UserCountryClient client.UserCountryQueryClient
UserRegionClient client.UserRegionClient
UserSocialClient client.UserSocialClient
UserHostClient client.UserHostClient
WalletClient client.WalletClient
@ -42,6 +46,8 @@ func New(config Config) *Handler {
roomGuardClient: config.RoomGuardClient,
roomQueryClient: config.RoomQueryClient,
userProfileClient: config.UserProfileClient,
userCountryClient: config.UserCountryClient,
userRegionClient: config.UserRegionClient,
userSocialClient: config.UserSocialClient,
userHostClient: config.UserHostClient,
walletClient: config.WalletClient,

View File

@ -63,12 +63,6 @@ type roomFeedRelationCursor struct {
// listRooms 按当前登录用户的服务端 region_id 查询房间发现列表。
func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) {
if h.userProfileClient == nil || h.roomQueryClient == nil {
// 列表入口必须同时依赖 user-service 区域归属和 room-service 读模型。
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
limit, ok := parseRoomListLimit(request.URL.Query().Get("limit"))
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
@ -79,6 +73,11 @@ func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
if h.userProfileClient == nil || h.roomQueryClient == nil || h.userRegionClient == nil || h.userCountryClient == nil {
// 列表入口必须同时依赖 user-service 用户区域、区域国家、国家主数据和 room-service 读模型。
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
viewerUserID := auth.UserIDFromContext(request.Context())
userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
@ -90,6 +89,16 @@ func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) {
httpkit.WriteRPCError(writer, request, err)
return
}
countries, err := h.roomListCountriesForUser(request, userResp.GetUser())
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
countryCode, ok := roomListCountryQuery(request, countries)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.roomQueryClient.ListRooms(request.Context(), &roomv1.ListRoomsRequest{
Meta: httpkit.RoomMeta(request, "", ""),
@ -99,12 +108,15 @@ func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) {
Cursor: request.URL.Query().Get("cursor"),
Limit: limit,
Query: roomListQuery(request),
CountryCode: countryCode,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, roomListDataFromProto(resp))
data := roomListDataFromProto(resp)
data.Countries = countries
httpkit.WriteOK(writer, request, data)
}
// listRoomFeeds 查询 Mine 页 visited/friend/following/followed 房间流。
@ -296,6 +308,123 @@ func roomListQuery(request *http.Request) string {
return strings.TrimSpace(request.URL.Query().Get("query"))
}
func (h *Handler) roomListCountriesForUser(request *http.Request, user *userv1.User) ([]roomListCountryData, error) {
if user == nil || user.GetRegionId() <= 0 {
// 当前用户区域来自 user-service 主数据;缺失时不能猜测区域国家列表。
return nil, xerr.New(xerr.InvalidArgument, "user region is required")
}
regionResp, err := h.userRegionClient.GetRegion(request.Context(), &userv1.GetRegionRequest{
Meta: httpkit.UserMeta(request, ""),
RegionId: user.GetRegionId(),
})
if err != nil {
return nil, err
}
countryResp, err := h.userCountryClient.ListRegistrationCountries(request.Context(), &userv1.ListRegistrationCountriesRequest{
Meta: httpkit.UserMeta(request, ""),
})
if err != nil {
return nil, err
}
return roomListCountriesFromProto(user, regionResp.GetRegion(), countryResp.GetCountries()), nil
}
func roomListCountriesFromProto(user *userv1.User, region *userv1.Region, countries []*userv1.Country) []roomListCountryData {
countriesByCode := make(map[string]*userv1.Country, len(countries))
for _, country := range countries {
code := normalizeRoomListCountryCode(country.GetCountryCode())
if code == "" {
continue
}
countriesByCode[code] = country
}
seen := make(map[string]struct{})
regionCodes := make([]string, 0, len(region.GetCountries()))
regionCodeSet := make(map[string]struct{}, len(region.GetCountries()))
if region != nil {
for _, rawCode := range region.GetCountries() {
code := normalizeRoomListCountryCode(rawCode)
if code == "" {
continue
}
regionCodes = append(regionCodes, code)
regionCodeSet[code] = struct{}{}
}
}
result := make([]roomListCountryData, 0, len(countriesByCode))
add := func(code string) {
code = normalizeRoomListCountryCode(code)
if code == "" {
return
}
if _, exists := seen[code]; exists {
return
}
seen[code] = struct{}{}
result = append(result, roomListCountryDataFromProto(code, countriesByCode[code]))
}
// 国家列表严格来自当前区域;用户国家命中区域配置时提前到首位,其余国家保持 user-service 区域顺序。
if user != nil {
if _, exists := regionCodeSet[normalizeRoomListCountryCode(user.GetCountry())]; exists {
add(user.GetCountry())
}
}
for _, code := range regionCodes {
add(code)
}
return result
}
func roomListCountryDataFromProto(code string, country *userv1.Country) roomListCountryData {
item := roomListCountryData{
CountryCode: code,
CountryFlag: roomProfileCountryFlag(code),
}
if country == nil {
return item
}
item.CountryID = country.GetCountryId()
item.CountryName = strings.TrimSpace(country.GetCountryName())
item.CountryDisplayName = strings.TrimSpace(country.GetCountryDisplayName())
if flag := strings.TrimSpace(country.GetFlag()); flag != "" {
item.CountryFlag = flag
}
item.SortOrder = country.GetSortOrder()
return item
}
func roomListCountryQuery(request *http.Request, countries []roomListCountryData) (string, bool) {
raw := strings.TrimSpace(request.URL.Query().Get("country"))
if raw == "" {
raw = strings.TrimSpace(request.URL.Query().Get("country_code"))
}
code := normalizeRoomListCountryCode(raw)
if code == "" {
return "", raw == ""
}
for _, country := range countries {
if normalizeRoomListCountryCode(country.CountryCode) == code {
return code, true
}
}
return "", false
}
func normalizeRoomListCountryCode(countryCode string) string {
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
if len(countryCode) < 2 || len(countryCode) > 3 {
return ""
}
for _, ch := range countryCode {
if ch < 'A' || ch > 'Z' {
return ""
}
}
return countryCode
}
// parseRoomListLimit 只做 HTTP 字符串到数字的传输层解析。
// 默认值和最大值仍由 room-service 统一裁剪,避免 gateway 和查询服务出现双份策略。
func parseRoomListLimit(raw string) (int32, bool) {
@ -611,7 +740,41 @@ func (h *Handler) getRoomRocket(writer http.ResponseWriter, request *http.Reques
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, roomRocketDataFromProto(resp))
resourceGroups, ok := h.resolveRoomRocketResourceGroups(writer, request, resp)
if !ok {
return
}
httpkit.WriteOK(writer, request, roomRocketDataFromProto(resp, resourceGroups))
}
// resolveRoomRocketResourceGroups 展开奖励池和最近中奖结果引用的资源组。
// room-service 只保存房间状态和奖励资源组 IDgateway 在 HTTP 出口补齐素材和钱包资产明细,避免 Room Cell 持有展示快照。
func (h *Handler) resolveRoomRocketResourceGroups(writer http.ResponseWriter, request *http.Request, resp *roomv1.GetRoomRocketResponse) (map[int64]*roomRocketResourceGroupData, bool) {
ids := roomRocketResourceGroupIDs(resp)
groups := make(map[int64]*roomRocketResourceGroupData, len(ids))
if len(ids) == 0 {
return groups, true
}
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return nil, false
}
for _, id := range ids {
groupResp, err := h.walletClient.GetResourceGroup(request.Context(), &walletv1.GetResourceGroupRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
GroupId: id,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return nil, false
}
group := roomRocketResourceGroupFromProto(groupResp.GetGroup())
if group != nil && group.GroupID > 0 {
groups[group.GroupID] = group
}
}
return groups, true
}
// handleRoomFollow 建立或取消当前用户对房间的关注关系。
@ -697,14 +860,15 @@ func (h *Handler) createRoom(writer http.ResponseWriter, request *http.Request)
roomID := appcode.NewScopedID(appcode.FromContext(request.Context()))
roomShortID := userResp.GetUser().GetDisplayUserId()
resp, err := h.roomClient.CreateRoom(request.Context(), &roomv1.CreateRoomRequest{
Meta: httpkit.RoomMeta(request, roomID, body.CommandID),
SeatCount: body.SeatCount,
Mode: body.Mode,
VisibleRegionId: userResp.GetUser().GetRegionId(),
RoomName: body.RoomName,
RoomAvatar: body.RoomAvatar,
RoomDescription: body.RoomDescription,
RoomShortId: roomShortID,
Meta: httpkit.RoomMeta(request, roomID, body.CommandID),
SeatCount: body.SeatCount,
Mode: body.Mode,
VisibleRegionId: userResp.GetUser().GetRegionId(),
OwnerCountryCode: userResp.GetUser().GetCountry(),
RoomName: body.RoomName,
RoomAvatar: body.RoomAvatar,
RoomDescription: body.RoomDescription,
RoomShortId: roomShortID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)

View File

@ -83,6 +83,21 @@ func TestCreateRoomDataIncludesExplicitIMGroupID(t *testing.T) {
}
}
func TestRoomSeatDataIncludesGiftValue(t *testing.T) {
seats := roomSeatDataFromSnapshot(&roomv1.RoomSnapshot{
RoomId: "room-seat-gift-value",
MicSeats: []*roomv1.SeatState{{
SeatNo: 3,
UserId: 1003,
GiftValue: 367,
}},
})
if len(seats) != 1 || seats[0].SeatNo != 3 || seats[0].UserID != "1003" || seats[0].GiftValue != 367 {
t.Fatalf("seat data must expose gift_value: %+v", seats)
}
}
func TestRoomProfileAgeFromBirth(t *testing.T) {
now := time.Date(2026, time.May, 27, 12, 0, 0, 0, time.UTC)
tests := []struct {

View File

@ -3,16 +3,28 @@ package roomapi
import (
"strconv"
"hyapp/pkg/imgroup"
"hyapp/pkg/tencentrtc"
roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
)
const joinRoomContributionRankLimit = 10
type roomListData struct {
Rooms []roomListItemData `json:"rooms"`
NextCursor string `json:"next_cursor,omitempty"`
Rooms []roomListItemData `json:"rooms"`
NextCursor string `json:"next_cursor,omitempty"`
Countries []roomListCountryData `json:"countries,omitempty"`
}
type roomListCountryData struct {
CountryID int64 `json:"country_id,omitempty"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name,omitempty"`
CountryDisplayName string `json:"country_display_name,omitempty"`
CountryFlag string `json:"country_flag,omitempty"`
SortOrder int32 `json:"sort_order,omitempty"`
}
type myRoomData struct {
@ -37,6 +49,8 @@ type roomListItemData struct {
VisibleRegionID int64 `json:"visible_region_id,omitempty"`
AppCode string `json:"app_code,omitempty"`
RoomShortID string `json:"room_short_id,omitempty"`
CountryCode string `json:"country_code,omitempty"`
CountryFlag string `json:"country_flag,omitempty"`
}
type createRoomData struct {
@ -191,11 +205,12 @@ type roomRocketLevelData struct {
}
type roomRocketRewardData struct {
RewardItemID string `json:"reward_item_id"`
ResourceGroupID int64 `json:"resource_group_id"`
Weight int64 `json:"weight,omitempty"`
DisplayName string `json:"display_name,omitempty"`
IconURL string `json:"icon_url,omitempty"`
RewardItemID string `json:"reward_item_id"`
ResourceGroupID int64 `json:"resource_group_id"`
ResourceGroup *roomRocketResourceGroupData `json:"resource_group,omitempty"`
Weight int64 `json:"weight,omitempty"`
DisplayName string `json:"display_name,omitempty"`
IconURL string `json:"icon_url,omitempty"`
}
type roomRocketStateData struct {
@ -229,14 +244,59 @@ type roomRocketPendingLaunchData struct {
}
type roomRocketRewardGrantData struct {
RewardRole string `json:"reward_role"`
UserID string `json:"user_id"`
RewardItemID string `json:"reward_item_id"`
ResourceGroupID int64 `json:"resource_group_id"`
DisplayName string `json:"display_name,omitempty"`
IconURL string `json:"icon_url,omitempty"`
GrantID string `json:"grant_id,omitempty"`
Status string `json:"status,omitempty"`
RewardRole string `json:"reward_role"`
UserID string `json:"user_id"`
RewardItemID string `json:"reward_item_id"`
ResourceGroupID int64 `json:"resource_group_id"`
ResourceGroup *roomRocketResourceGroupData `json:"resource_group,omitempty"`
DisplayName string `json:"display_name,omitempty"`
IconURL string `json:"icon_url,omitempty"`
GrantID string `json:"grant_id,omitempty"`
Status string `json:"status,omitempty"`
}
type roomRocketResourceData struct {
ResourceID int64 `json:"resource_id"`
ResourceCode string `json:"resource_code"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
Status string `json:"status"`
Grantable bool `json:"grantable"`
GrantStrategy string `json:"grant_strategy"`
WalletAssetType string `json:"wallet_asset_type,omitempty"`
WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"`
UsageScopes []string `json:"usage_scopes"`
AssetURL string `json:"asset_url"`
PreviewURL string `json:"preview_url"`
AnimationURL string `json:"animation_url"`
MetadataJSON string `json:"metadata_json"`
SortOrder int32 `json:"sort_order"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type roomRocketResourceGroupItemData struct {
GroupItemID int64 `json:"group_item_id"`
ItemType string `json:"item_type"`
ResourceID int64 `json:"resource_id"`
Resource *roomRocketResourceData `json:"resource,omitempty"`
WalletAssetType string `json:"wallet_asset_type,omitempty"`
WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
SortOrder int32 `json:"sort_order"`
}
type roomRocketResourceGroupData struct {
GroupID int64 `json:"group_id"`
GroupCode string `json:"group_code"`
Name string `json:"name"`
Status string `json:"status"`
Description string `json:"description"`
SortOrder int32 `json:"sort_order"`
Items []roomRocketResourceGroupItemData `json:"items"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type roomGiftRecipientData struct {
@ -347,6 +407,7 @@ type roomSeatData struct {
MicMuted bool `json:"mic_muted"`
SeatStatus string `json:"seat_status,omitempty"`
MicHeartbeatAtMS int64 `json:"mic_heartbeat_at_ms,omitempty"`
GiftValue int64 `json:"gift_value"`
}
type roomRankItemData struct {
@ -406,15 +467,15 @@ func createRoomDataFromProto(resp *roomv1.CreateRoomResponse) createRoomData {
}
}
func roomRocketDataFromProto(resp *roomv1.GetRoomRocketResponse) roomRocketData {
func roomRocketDataFromProto(resp *roomv1.GetRoomRocketResponse, resourceGroups map[int64]*roomRocketResourceGroupData) roomRocketData {
if resp == nil || resp.GetRocket() == nil {
return roomRocketData{}
}
rocket := resp.GetRocket()
return roomRocketData{
Enabled: rocket.GetEnabled(),
Levels: roomRocketLevelsFromProto(rocket.GetLevels()),
State: roomRocketStateFromProto(rocket.GetState()),
Levels: roomRocketLevelsFromProto(rocket.GetLevels(), resourceGroups),
State: roomRocketStateFromProto(rocket.GetState(), resourceGroups),
BroadcastScope: rocket.GetBroadcastScope(),
LaunchDelayMS: rocket.GetLaunchDelayMs(),
BroadcastDelayMS: rocket.GetBroadcastDelayMs(),
@ -423,7 +484,7 @@ func roomRocketDataFromProto(resp *roomv1.GetRoomRocketResponse) roomRocketData
}
}
func roomRocketLevelsFromProto(levels []*roomv1.RoomRocketLevel) []roomRocketLevelData {
func roomRocketLevelsFromProto(levels []*roomv1.RoomRocketLevel, resourceGroups map[int64]*roomRocketResourceGroupData) []roomRocketLevelData {
out := make([]roomRocketLevelData, 0, len(levels))
for _, level := range levels {
out = append(out, roomRocketLevelData{
@ -433,20 +494,21 @@ func roomRocketLevelsFromProto(levels []*roomv1.RoomRocketLevel) []roomRocketLev
AnimationURL: level.GetAnimationUrl(),
LaunchAnimationURL: level.GetLaunchAnimationUrl(),
LaunchedImageURL: level.GetLaunchedImageUrl(),
InRoomRewards: roomRocketRewardsFromProto(level.GetInRoomRewards()),
Top1Rewards: roomRocketRewardsFromProto(level.GetTop1Rewards()),
IgniterRewards: roomRocketRewardsFromProto(level.GetIgniterRewards()),
InRoomRewards: roomRocketRewardsFromProto(level.GetInRoomRewards(), resourceGroups),
Top1Rewards: roomRocketRewardsFromProto(level.GetTop1Rewards(), resourceGroups),
IgniterRewards: roomRocketRewardsFromProto(level.GetIgniterRewards(), resourceGroups),
})
}
return out
}
func roomRocketRewardsFromProto(rewards []*roomv1.RoomRocketRewardItem) []roomRocketRewardData {
func roomRocketRewardsFromProto(rewards []*roomv1.RoomRocketRewardItem, resourceGroups map[int64]*roomRocketResourceGroupData) []roomRocketRewardData {
out := make([]roomRocketRewardData, 0, len(rewards))
for _, reward := range rewards {
out = append(out, roomRocketRewardData{
RewardItemID: reward.GetRewardItemId(),
ResourceGroupID: reward.GetResourceGroupId(),
ResourceGroup: resourceGroups[reward.GetResourceGroupId()],
Weight: reward.GetWeight(),
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
@ -455,7 +517,7 @@ func roomRocketRewardsFromProto(rewards []*roomv1.RoomRocketRewardItem) []roomRo
return out
}
func roomRocketStateFromProto(state *roomv1.RoomRocketState) roomRocketStateData {
func roomRocketStateFromProto(state *roomv1.RoomRocketState, resourceGroups map[int64]*roomRocketResourceGroupData) roomRocketStateData {
if state == nil {
return roomRocketStateData{}
}
@ -472,7 +534,7 @@ func roomRocketStateFromProto(state *roomv1.RoomRocketState) roomRocketStateData
IgniterUserID: formatOptionalUserID(state.GetIgniterUserId()),
RocketID: state.GetRocketId(),
ConfigVersion: state.GetConfigVersion(),
LastRewards: roomRocketRewardGrantsFromProto(state.GetLastRewards()),
LastRewards: roomRocketRewardGrantsFromProto(state.GetLastRewards(), resourceGroups),
PendingLaunches: roomRocketPendingLaunchesFromProto(state.GetPendingLaunches()),
}
}
@ -496,7 +558,7 @@ func roomRocketPendingLaunchesFromProto(launches []*roomv1.RoomRocketPendingLaun
return out
}
func roomRocketRewardGrantsFromProto(rewards []*roomv1.RoomRocketRewardGrant) []roomRocketRewardGrantData {
func roomRocketRewardGrantsFromProto(rewards []*roomv1.RoomRocketRewardGrant, resourceGroups map[int64]*roomRocketResourceGroupData) []roomRocketRewardGrantData {
out := make([]roomRocketRewardGrantData, 0, len(rewards))
for _, reward := range rewards {
out = append(out, roomRocketRewardGrantData{
@ -504,6 +566,7 @@ func roomRocketRewardGrantsFromProto(rewards []*roomv1.RoomRocketRewardGrant) []
UserID: formatOptionalUserID(reward.GetUserId()),
RewardItemID: reward.GetRewardItemId(),
ResourceGroupID: reward.GetResourceGroupId(),
ResourceGroup: resourceGroups[reward.GetResourceGroupId()],
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
GrantID: reward.GetGrantId(),
@ -513,6 +576,95 @@ func roomRocketRewardGrantsFromProto(rewards []*roomv1.RoomRocketRewardGrant) []
return out
}
func roomRocketResourceGroupIDs(resp *roomv1.GetRoomRocketResponse) []int64 {
if resp == nil || resp.GetRocket() == nil {
return nil
}
seen := map[int64]struct{}{}
ids := make([]int64, 0)
add := func(id int64) {
if id <= 0 {
return
}
if _, exists := seen[id]; exists {
return
}
seen[id] = struct{}{}
ids = append(ids, id)
}
for _, level := range resp.GetRocket().GetLevels() {
for _, reward := range level.GetInRoomRewards() {
add(reward.GetResourceGroupId())
}
for _, reward := range level.GetTop1Rewards() {
add(reward.GetResourceGroupId())
}
for _, reward := range level.GetIgniterRewards() {
add(reward.GetResourceGroupId())
}
}
for _, reward := range resp.GetRocket().GetState().GetLastRewards() {
add(reward.GetResourceGroupId())
}
return ids
}
func roomRocketResourceGroupFromProto(group *walletv1.ResourceGroup) *roomRocketResourceGroupData {
if group == nil {
return nil
}
items := make([]roomRocketResourceGroupItemData, 0, len(group.GetItems()))
for _, item := range group.GetItems() {
items = append(items, roomRocketResourceGroupItemData{
GroupItemID: item.GetGroupItemId(),
ItemType: item.GetItemType(),
ResourceID: item.GetResourceId(),
Resource: roomRocketResourceFromProto(item.GetResource()),
WalletAssetType: item.GetWalletAssetType(),
WalletAssetAmount: item.GetWalletAssetAmount(),
Quantity: item.GetQuantity(),
DurationMS: item.GetDurationMs(),
SortOrder: item.GetSortOrder(),
})
}
return &roomRocketResourceGroupData{
GroupID: group.GetGroupId(),
GroupCode: group.GetGroupCode(),
Name: group.GetName(),
Status: group.GetStatus(),
Description: group.GetDescription(),
SortOrder: group.GetSortOrder(),
Items: items,
CreatedAtMS: group.GetCreatedAtMs(),
UpdatedAtMS: group.GetUpdatedAtMs(),
}
}
func roomRocketResourceFromProto(item *walletv1.Resource) *roomRocketResourceData {
if item == nil {
return nil
}
return &roomRocketResourceData{
ResourceID: item.GetResourceId(),
ResourceCode: item.GetResourceCode(),
ResourceType: item.GetResourceType(),
Name: item.GetName(),
Status: item.GetStatus(),
Grantable: item.GetGrantable(),
GrantStrategy: item.GetGrantStrategy(),
WalletAssetType: item.GetWalletAssetType(),
WalletAssetAmount: item.GetWalletAssetAmount(),
UsageScopes: item.GetUsageScopes(),
AssetURL: item.GetAssetUrl(),
PreviewURL: item.GetPreviewUrl(),
AnimationURL: item.GetAnimationUrl(),
MetadataJSON: item.GetMetadataJson(),
SortOrder: item.GetSortOrder(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func roomListItemDataFromProto(room *roomv1.RoomListItem) roomListItemData {
if room == nil {
return roomListItemData{}
@ -534,6 +686,8 @@ func roomListItemDataFromProto(room *roomv1.RoomListItem) roomListItemData {
VisibleRegionID: room.GetVisibleRegionId(),
AppCode: room.GetAppCode(),
RoomShortID: room.GetRoomShortId(),
CountryCode: room.GetCountryCode(),
CountryFlag: roomProfileCountryFlag(room.GetCountryCode()),
}
}
@ -689,6 +843,7 @@ func roomSeatDataFromSnapshot(snapshot *roomv1.RoomSnapshot) []roomSeatData {
MicMuted: seat.GetMicMuted(),
SeatStatus: seat.GetSeatStatus(),
MicHeartbeatAtMS: seat.GetMicHeartbeatAtMs(),
GiftValue: seat.GetGiftValue(),
})
}
return items
@ -933,8 +1088,12 @@ func roomSeatCounts(snapshot *roomv1.RoomSnapshot) (int32, int32) {
}
func roomIMGroupID(roomID string) string {
// 当前房间群规则固定为腾讯云 IM GroupID 等于内部 room_id显式下发给客户端避免端侧硬编码规则。
return roomID
// 房间 IM 群 ID 必须等于内部 room_id线上腾讯云已按这个 ID 建群,客户端也按这个字段入群。
groupID, err := imgroup.RoomGroupID(roomID)
if err != nil {
return roomID
}
return groupID
}
func formatOptionalUserID(userID int64) string {

View File

@ -26,6 +26,8 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
RoomGuardClient: h.roomGuardClient,
RoomQueryClient: h.roomQueryClient,
UserProfileClient: h.userProfileClient,
UserCountryClient: h.userCountryClient,
UserRegionClient: h.userRegionClient,
UserSocialClient: h.userSocialClient,
UserHostClient: h.userHostClient,
WalletClient: h.walletClient,
@ -92,6 +94,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
WalletClient: h.walletClient,
UserHostClient: h.userHostClient,
UserProfileClient: h.userProfileClient,
GrowthLevelClient: h.growthLevelClient,
})
appAPI := appapi.New(appapi.Config{
AppConfigReader: h.appConfigReader,

View File

@ -5,6 +5,7 @@ import (
"strings"
userv1 "hyapp.local/api/proto/user/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
)
@ -42,21 +43,32 @@ type cpApplicationData struct {
}
type cpRelationshipData struct {
RelationshipID string `json:"relationship_id"`
RelationType string `json:"relation_type"`
Status string `json:"status"`
Me *cpUserProfileData `json:"me,omitempty"`
Partner *cpUserProfileData `json:"partner,omitempty"`
IntimacyValue int64 `json:"intimacy_value"`
Level int32 `json:"level"`
FormedAtMS int64 `json:"formed_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
RelationshipID string `json:"relationship_id"`
RelationType string `json:"relation_type"`
Status string `json:"status"`
Me *cpUserProfileData `json:"me,omitempty"`
Partner *cpUserProfileData `json:"partner,omitempty"`
IntimacyValue int64 `json:"intimacy_value"`
Level int32 `json:"level"`
CurrentLevelThreshold int64 `json:"current_level_threshold"`
NextLevelThreshold int64 `json:"next_level_threshold"`
NeededForNextLevel int64 `json:"needed_for_next_level"`
LevelProgressPercent int32 `json:"level_progress_percent"`
MaxLevel bool `json:"max_level"`
BreakupCostCoins int64 `json:"breakup_cost_coins"`
FormedAtMS int64 `json:"formed_at_ms"`
EndedAtMS int64 `json:"ended_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type cpRejectRequest struct {
Reason string `json:"reason"`
}
type cpBreakRelationshipRequest struct {
CommandID string `json:"command_id"`
}
// listCPApplications 返回当前用户收到或发出的 CP/兄弟/姐妹申请。
func (h *Handler) listCPApplications(writer http.ResponseWriter, request *http.Request) {
if h.userCPClient == nil {
@ -177,6 +189,96 @@ func (h *Handler) listCPRelationships(writer http.ResponseWriter, request *http.
})
}
// breakCPRelationship 先在 user-service 创建解除占位,再扣钱包,最后确认关系 ended避免并发请求重复扣费。
func (h *Handler) breakCPRelationship(writer http.ResponseWriter, request *http.Request) {
if h.userCPClient == nil || h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
relationshipID := strings.TrimSpace(request.PathValue("relationship_id"))
if relationshipID == "" {
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
return
}
var body cpBreakRelationshipRequest
if !httpkit.Decode(writer, request, &body) {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "command_id is required")
return
}
userID := auth.UserIDFromContext(request.Context())
meta := httpkit.UserMeta(request, "")
prepare, err := h.userCPClient.PrepareBreakCPRelationship(request.Context(), &userv1.PrepareBreakCPRelationshipRequest{
Meta: meta,
UserId: userID,
RelationshipId: relationshipID,
CommandId: commandID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
if prepare.GetStatus() == "confirmed" {
httpkit.WriteOK(writer, request, map[string]any{
"relationship": cpRelationshipFromProto(prepare.GetRelationship()),
"breakup_cost_coins": prepare.GetBreakupCostCoins(),
"wallet_transaction_id": prepare.GetWalletTransactionId(),
"coin_balance_after": prepare.GetCoinBalanceAfter(),
})
return
}
walletTransactionID := ""
coinBalanceAfter := int64(0)
cost := prepare.GetBreakupCostCoins()
if cost > 0 {
walletResp, walletErr := h.walletClient.DebitCPBreakupFee(request.Context(), &walletv1.DebitCPBreakupFeeRequest{
CommandId: commandID,
AppCode: meta.GetAppCode(),
UserId: userID,
RelationshipId: relationshipID,
RelationType: prepare.GetRelationship().GetRelationType(),
Amount: cost,
})
if walletErr != nil {
// 钱包失败表示关系不应解除;取消 pending 占位后把真实钱包错误返回给客户端。
_, _ = h.userCPClient.CancelBreakCPRelationship(request.Context(), &userv1.CancelBreakCPRelationshipRequest{
Meta: meta,
UserId: userID,
RelationshipId: relationshipID,
CommandId: commandID,
Reason: "wallet_debit_failed",
})
httpkit.WriteRPCError(writer, request, walletErr)
return
}
walletTransactionID = walletResp.GetTransactionId()
coinBalanceAfter = walletResp.GetCoinBalanceAfter()
}
confirmed, err := h.userCPClient.ConfirmBreakCPRelationship(request.Context(), &userv1.ConfirmBreakCPRelationshipRequest{
Meta: meta,
UserId: userID,
RelationshipId: relationshipID,
CommandId: commandID,
WalletTransactionId: walletTransactionID,
PaidCoinAmount: cost,
CoinBalanceAfter: coinBalanceAfter,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{
"relationship": cpRelationshipFromProto(confirmed.GetRelationship()),
"breakup_cost_coins": confirmed.GetBreakupCostCoins(),
"wallet_transaction_id": confirmed.GetWalletTransactionId(),
"coin_balance_after": confirmed.GetCoinBalanceAfter(),
})
}
func cpApplicationFromProto(item *userv1.CPApplication) cpApplicationData {
if item == nil {
return cpApplicationData{}
@ -202,15 +304,22 @@ func cpRelationshipFromProto(item *userv1.CPRelationship) cpRelationshipData {
return cpRelationshipData{}
}
return cpRelationshipData{
RelationshipID: item.GetRelationshipId(),
RelationType: item.GetRelationType(),
Status: item.GetStatus(),
Me: cpUserProfileFromProto(item.GetMe()),
Partner: cpUserProfileFromProto(item.GetPartner()),
IntimacyValue: item.GetIntimacyValue(),
Level: item.GetLevel(),
FormedAtMS: item.GetFormedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
RelationshipID: item.GetRelationshipId(),
RelationType: item.GetRelationType(),
Status: item.GetStatus(),
Me: cpUserProfileFromProto(item.GetMe()),
Partner: cpUserProfileFromProto(item.GetPartner()),
IntimacyValue: item.GetIntimacyValue(),
Level: item.GetLevel(),
CurrentLevelThreshold: item.GetCurrentLevelThreshold(),
NextLevelThreshold: item.GetNextLevelThreshold(),
NeededForNextLevel: item.GetNeededForNextLevel(),
LevelProgressPercent: item.GetLevelProgressPercent(),
MaxLevel: item.GetMaxLevel(),
BreakupCostCoins: item.GetBreakupCostCoins(),
FormedAtMS: item.GetFormedAtMs(),
EndedAtMS: item.GetEndedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}

View File

@ -0,0 +1,37 @@
package userapi
import (
"testing"
userv1 "hyapp.local/api/proto/user/v1"
)
func TestCPRelationshipFromProtoIncludesLevelProgress(t *testing.T) {
item := &userv1.CPRelationship{
RelationshipId: "cp_rel:1:2",
RelationType: "brother",
Status: "active",
IntimacyValue: 180,
Level: 2,
CurrentLevelThreshold: 100,
NextLevelThreshold: 300,
NeededForNextLevel: 120,
LevelProgressPercent: 40,
MaxLevel: false,
BreakupCostCoins: 88,
FormedAtMs: 1700000000000,
EndedAtMs: 1700000002000,
UpdatedAtMs: 1700000001000,
}
got := cpRelationshipFromProto(item)
if got.CurrentLevelThreshold != 100 ||
got.NextLevelThreshold != 300 ||
got.NeededForNextLevel != 120 ||
got.LevelProgressPercent != 40 ||
got.MaxLevel ||
got.BreakupCostCoins != 88 ||
got.EndedAtMS != 1700000002000 {
t.Fatalf("relationship progress fields not mapped: %+v", got)
}
}

View File

@ -88,6 +88,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers {
AcceptCPApplication: h.acceptCPApplication,
RejectCPApplication: h.rejectCPApplication,
ListCPRelationships: h.listCPRelationships,
BreakCPRelationship: h.breakCPRelationship,
GetMyProfile: h.getMyProfile,
GetMyAppearance: h.getMyAppearance,
GetUserAppearance: h.getUserAppearance,

View File

@ -189,10 +189,10 @@ func validateDelivery(delivery CPDelivery) error {
len(delivery.PayloadJSON) == 0 {
return xerr.New(xerr.InvalidArgument, "cp delivery is incomplete")
}
if delivery.Channel == channelTencentIMC2C && delivery.TargetUserID <= 0 {
if isTencentIMC2CChannel(delivery.Channel) && delivery.TargetUserID <= 0 {
return xerr.New(xerr.InvalidArgument, "cp c2c delivery target_user_id is required")
}
if delivery.Channel == channelTencentIMC2C && delivery.SenderUserID <= 0 {
if isTencentIMC2CChannel(delivery.Channel) && delivery.SenderUserID <= 0 {
return xerr.New(xerr.InvalidArgument, "cp c2c delivery sender_user_id is required")
}
if (delivery.Channel == channelTencentIMRoom || delivery.Channel == channelTencentIMRegion) && strings.TrimSpace(delivery.GroupID) == "" {
@ -201,6 +201,15 @@ func validateDelivery(delivery CPDelivery) error {
return nil
}
func isTencentIMC2CChannel(channel string) bool {
switch channel {
case channelTencentIMC2C, channelTencentIMC2CAcceptor:
return true
default:
return false
}
}
func truncateError(value string) string {
value = strings.TrimSpace(value)
if len(value) <= 512 {

View File

@ -22,9 +22,10 @@ const (
eventApplicationRejected = "UserCPApplicationRejected"
eventRelationshipCreated = "UserCPRelationshipCreated"
channelTencentIMC2C = "tencent_im_c2c"
channelTencentIMRoom = "tencent_im_room"
channelTencentIMRegion = "tencent_im_region"
channelTencentIMC2C = "tencent_im_c2c"
channelTencentIMC2CAcceptor = "tencent_im_c2c_acceptor"
channelTencentIMRoom = "tencent_im_room"
channelTencentIMRegion = "tencent_im_region"
noticeTypeApplicationCreated = "cp_application_created"
noticeTypeApplicationAccepted = "cp_application_accepted"
@ -99,7 +100,7 @@ func (s *Service) publishDelivery(ctx context.Context, delivery CPDelivery, opti
defer cancel()
var err error
switch delivery.Channel {
case channelTencentIMC2C:
case channelTencentIMC2C, channelTencentIMC2CAcceptor:
err = s.publisher.PublishUserCustomMessage(publishCtx, tencentim.CustomUserMessage{
ToAccount: tencentim.FormatUserID(delivery.TargetUserID),
FromAccount: tencentim.FormatUserID(delivery.SenderUserID),
@ -246,7 +247,7 @@ func deliveriesFromMessage(message usermq.UserOutboxMessage, groupIDPrefix strin
if err != nil {
return nil, err
}
return applicationDecisionDeliveries(appCode, message, application, payload, noticeTypeApplicationAccepted), nil
return applicationAcceptedDeliveries(appCode, message, application, payload), nil
case eventApplicationRejected:
application, err := applicationFromObject(firstObject(root, "application", "Application"))
if err != nil {
@ -309,6 +310,58 @@ func applicationDecisionDeliveries(appCode string, message usermq.UserOutboxMess
return deliveries
}
func applicationAcceptedDeliveries(appCode string, message usermq.UserOutboxMessage, application cpApplication, payload map[string]any) []CPDelivery {
// 同意成功后双方都要在 C2C 会话里看到一条消息notice_delivery_events 的幂等键只有 event_id + channel
// 所以第二条 C2C 使用独立内部 channel但 publishDelivery 仍按腾讯 IM 单聊投递。
requesterPayload := cloneJSONMap(payload)
requesterPayload["c2c_notice_role"] = "accepted_to_requester"
requesterPayload["message_text"] = "我同意了你"
acceptorPayload := cloneJSONMap(payload)
acceptorPayload["c2c_notice_role"] = "accepted_to_acceptor"
acceptorPayload["message_text"] = "对方已同意你的申请"
deliveries := []CPDelivery{
{
AppCode: appCode,
EventID: message.EventID,
EventType: message.EventType,
Channel: channelTencentIMC2C,
NoticeType: noticeTypeApplicationAccepted,
SenderUserID: application.Target.UserID,
TargetUserID: application.Requester.UserID,
PayloadJSON: mustJSON(requesterPayload),
CreatedAtMS: message.OccurredAtMS,
},
{
AppCode: appCode,
EventID: message.EventID,
EventType: message.EventType,
Channel: channelTencentIMC2CAcceptor,
NoticeType: noticeTypeApplicationAccepted,
SenderUserID: application.Requester.UserID,
TargetUserID: application.Target.UserID,
PayloadJSON: mustJSON(acceptorPayload),
CreatedAtMS: message.OccurredAtMS,
},
}
if application.RoomID == "" {
return deliveries
}
roomPayload := cloneJSONMap(payload)
roomPayload["recipient_user_id"] = strconv.FormatInt(application.Requester.UserID, 10)
deliveries = append(deliveries, CPDelivery{
AppCode: appCode,
EventID: message.EventID,
EventType: message.EventType,
Channel: channelTencentIMRoom,
NoticeType: noticeTypeApplicationAccepted,
TargetUserID: application.Requester.UserID,
GroupID: application.RoomID,
PayloadJSON: mustJSON(roomPayload),
CreatedAtMS: message.OccurredAtMS,
})
return deliveries
}
func relationshipCreatedDelivery(appCode string, message usermq.UserOutboxMessage, application cpApplication, relationshipID string, groupID string) ([]CPDelivery, error) {
payload, err := cpRelationshipBroadcastPayload(message, application, relationshipID, groupID)
if err != nil {

View File

@ -39,7 +39,7 @@ func TestProcessApplicationCreatedPublishesC2CAndRoomIM(t *testing.T) {
}
}
func TestProcessApplicationAcceptedPublishesRequesterC2CAndRoomIM(t *testing.T) {
func TestProcessApplicationAcceptedPublishesMutualC2CAndRoomIM(t *testing.T) {
repo := &fakeRepository{}
publisher := &fakePublisher{}
service := New(Config{NodeID: "notice-test"}, repo, publisher)
@ -56,13 +56,27 @@ func TestProcessApplicationAcceptedPublishesRequesterC2CAndRoomIM(t *testing.T)
if err != nil || !handled {
t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err)
}
if len(publisher.userMessages) != 1 || publisher.userMessages[0].FromAccount != "1002" || publisher.userMessages[0].ToAccount != "1001" {
t.Fatalf("expected requester c2c message, got %+v", publisher.userMessages)
if len(publisher.userMessages) != 2 {
t.Fatalf("expected mutual c2c messages, got %+v", publisher.userMessages)
}
if publisher.userMessages[0].FromAccount != "1002" || publisher.userMessages[0].ToAccount != "1001" {
t.Fatalf("expected acceptor -> requester c2c message, got %+v", publisher.userMessages[0])
}
requesterPayload := mustObject(string(publisher.userMessages[0].PayloadJSON))
if requesterPayload["c2c_notice_role"] != "accepted_to_requester" || requesterPayload["message_text"] != "我同意了你" {
t.Fatalf("requester c2c payload mismatch: %+v", requesterPayload)
}
if publisher.userMessages[1].FromAccount != "1001" || publisher.userMessages[1].ToAccount != "1002" {
t.Fatalf("expected requester -> acceptor c2c message, got %+v", publisher.userMessages[1])
}
acceptorPayload := mustObject(string(publisher.userMessages[1].PayloadJSON))
if acceptorPayload["c2c_notice_role"] != "accepted_to_acceptor" || acceptorPayload["message_text"] != "对方已同意你的申请" {
t.Fatalf("acceptor c2c payload mismatch: %+v", acceptorPayload)
}
if len(publisher.groupMessages) != 1 || publisher.groupMessages[0].GroupID != "room-100" {
t.Fatalf("expected requester room group message, got %+v", publisher.groupMessages)
}
if len(repo.delivered) != 2 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMRoom {
if len(repo.delivered) != 3 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMC2CAcceptor || repo.delivered[2].Channel != channelTencentIMRoom {
t.Fatalf("delivery channels mismatch: %+v", repo.delivered)
}
}

View File

@ -68,7 +68,7 @@ rocketmq:
tencent_im_consumer_group: "hyapp-room-im-bridge"
consumer_max_reconsume_times: 16
rocket_launch:
enabled: false
enabled: true
topic: "hyapp_room_rocket_launch"
producer_group: "hyapp-room-rocket-launch-producer"
consumer_group: "hyapp-room-rocket-launch"

View File

@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS room_list_entries (
room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
room_short_id VARCHAR(32) NOT NULL DEFAULT '' COMMENT '房间短号 ID',
visible_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '可见区域 ID',
owner_country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT '房主国家码',
owner_user_id BIGINT NOT NULL COMMENT '房主用户 ID',
title VARCHAR(128) NOT NULL DEFAULT '' COMMENT '标题',
cover_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 URL',
@ -56,6 +57,8 @@ CREATE TABLE IF NOT EXISTS room_list_entries (
KEY idx_room_list_short_id (app_code, room_short_id),
KEY idx_room_list_region_hot (app_code, visible_region_id, status, sort_score DESC, room_id),
KEY idx_room_list_region_new (app_code, visible_region_id, status, created_at_ms DESC, room_id),
KEY idx_room_list_region_country_hot (app_code, visible_region_id, owner_country_code, status, sort_score DESC, room_id),
KEY idx_room_list_region_country_new (app_code, visible_region_id, owner_country_code, status, created_at_ms DESC, room_id),
KEY idx_room_list_owner (app_code, owner_user_id, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间列表项表';

View File

@ -7,6 +7,7 @@ import (
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
"hyapp/pkg/imgroup"
"hyapp/pkg/tencentim"
)
@ -28,7 +29,11 @@ func (p *TencentIMPublisher) EnsureRoomGroup(ctx context.Context, roomID string,
return nil
}
return p.client.EnsureRoomGroup(ctx, roomID, ownerUserID)
groupID, err := imgroup.RoomGroupID(roomID)
if err != nil {
return err
}
return p.client.EnsureRoomGroup(ctx, groupID, ownerUserID)
}
// PublishRoomEvent 把房间系统事件发送到腾讯云 IM 群。
@ -38,7 +43,22 @@ func (p *TencentIMPublisher) PublishRoomEvent(ctx context.Context, event tencent
return nil
}
return p.client.PublishRoomEvent(ctx, event)
groupID, err := imgroup.RoomGroupID(event.RoomID)
if err != nil {
return err
}
event.GroupID = groupID
if err := p.client.PublishRoomEvent(ctx, event); err != nil {
if !tencentim.IsRESTErrorCode(err, 10010, 10015) {
return err
}
// 历史房间可能先产生 outbox 事件、但腾讯云群没有成功创建;保持原 GroupID补建后只重发一次。
if ensureErr := p.client.EnsureRoomGroup(ctx, groupID, 0); ensureErr != nil {
return fmt.Errorf("ensure room im group after publish failure: %w", ensureErr)
}
return p.client.PublishRoomEvent(ctx, event)
}
return nil
}
// PublishOutboxEvent 补偿投递 room_outbox 中可转成房间系统消息的事件。
@ -52,7 +72,7 @@ func (p *TencentIMPublisher) PublishOutboxEvent(ctx context.Context, envelope *r
return err
}
// 建群是腾讯 IM 的外部副作用,必须留在 outbox worker 内,不能阻塞 CreateRoom 返回。
return p.client.EnsureRoomGroup(ctx, envelope.GetRoomId(), body.GetOwnerUserId())
return p.EnsureRoomGroup(ctx, envelope.GetRoomId(), body.GetOwnerUserId())
}
event, publish, err := roomEventFromEnvelope(envelope)
@ -61,12 +81,22 @@ func (p *TencentIMPublisher) PublishOutboxEvent(ctx context.Context, envelope *r
}
if envelope.GetEventType() == "RoomUserKicked" {
// KickUser 的安全边界除了系统消息,还必须补偿移除腾讯云 IM 群成员。
if err := p.client.DeleteRoomGroupMember(ctx, envelope.GetRoomId(), event.TargetUserID); err != nil {
groupID, err := imgroup.RoomGroupID(envelope.GetRoomId())
if err != nil {
return err
}
if err := p.client.DeleteGroupMember(ctx, groupID, event.TargetUserID); err != nil {
if !tencentim.IsRESTErrorCode(err, 10010, 10015) {
return err
}
// 群缺失时没有成员可删,先补建群,再继续投递 kick 系统消息以推进 outbox。
if ensureErr := p.client.EnsureRoomGroup(ctx, groupID, 0); ensureErr != nil {
return fmt.Errorf("ensure room im group after member removal failure: %w", ensureErr)
}
}
}
return p.client.PublishRoomEvent(ctx, event)
return p.PublishRoomEvent(ctx, event)
}
// noopRoomEventPublisher 是本地和测试路径的房间消息发布占位实现。
@ -163,6 +193,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
"publish_event_time_ms": fmt.Sprintf("%d", body.GetPublishEventTimeMs()),
"mic_muted": fmt.Sprintf("%t", body.GetMicMuted()),
"seat_status": body.GetSeatStatus(),
"target_gift_value": fmt.Sprintf("%d", body.GetTargetGiftValue()),
}
return base, true, nil
case "RoomMicSeatLocked":
@ -270,6 +301,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
"gift_count": fmt.Sprintf("%d", body.GetGiftCount()),
"coin_spent": fmt.Sprintf("%d", body.GetCoinSpent()),
"billing_receipt_id": body.GetBillingReceiptId(),
"target_gift_value": fmt.Sprintf("%d", body.GetTargetGiftValue()),
}
return base, true, nil
case "RoomRocketFuelChanged":

View File

@ -3,6 +3,7 @@ package integration
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"testing"
@ -87,6 +88,56 @@ func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) {
}
}
func TestRoomGiftSentCarriesTargetGiftValueInIMAttributes(t *testing.T) {
record, err := outbox.Build("room-gift-value", "RoomGiftSent", 8, time.Now(), &roomeventsv1.RoomGiftSent{
SenderUserId: 1001,
TargetUserId: 1002,
GiftId: "gift_rose",
GiftCount: 1,
GiftValue: 117,
TargetGiftValue: 367,
})
if err != nil {
t.Fatalf("Build RoomGiftSent envelope failed: %v", err)
}
event, publish, err := roomEventFromEnvelope(record.Envelope)
if err != nil {
t.Fatalf("RoomGiftSent should decode: %v", err)
}
if !publish {
t.Fatal("RoomGiftSent must publish Tencent IM message")
}
if event.EventType != "room_gift_sent" || event.GiftValue != 117 || event.Attributes["target_gift_value"] != "367" {
t.Fatalf("gift IM event target gift value mismatch: %+v", event)
}
}
func TestRoomMicChangedCarriesTargetGiftValueInIMAttributes(t *testing.T) {
record, err := outbox.Build("room-mic-value", "RoomMicChanged", 9, time.Now(), &roomeventsv1.RoomMicChanged{
ActorUserId: 1001,
TargetUserId: 1002,
FromSeat: 2,
ToSeat: 5,
Action: "change",
TargetGiftValue: 367,
})
if err != nil {
t.Fatalf("Build RoomMicChanged envelope failed: %v", err)
}
event, publish, err := roomEventFromEnvelope(record.Envelope)
if err != nil {
t.Fatalf("RoomMicChanged should decode: %v", err)
}
if !publish {
t.Fatal("RoomMicChanged must publish Tencent IM message")
}
if event.EventType != "room_mic_changed" || event.SeatNo != 5 || event.Attributes["target_gift_value"] != "367" {
t.Fatalf("mic IM event target gift value mismatch: %+v", event)
}
}
func TestRoomPasswordChangedSkipsRoomGroupIM(t *testing.T) {
record, err := outbox.Build("room-lock", "RoomPasswordChanged", 8, time.Now(), &roomeventsv1.RoomPasswordChanged{
ActorUserId: 42,
@ -108,6 +159,7 @@ func TestRoomPasswordChangedSkipsRoomGroupIM(t *testing.T) {
func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
paths := make([]string, 0, 2)
bodies := make([]map[string]any, 0, 2)
client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
SDKAppID: 1400000000,
SecretKey: "secret",
@ -115,6 +167,11 @@ func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
AdminUserSigTTL: time.Hour,
HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
paths = append(paths, request.URL.Path)
var body map[string]any
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
t.Fatalf("decode request body failed: %v", err)
}
bodies = append(bodies, body)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))),
@ -141,6 +198,72 @@ func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
if len(paths) != 2 || paths[0] != "/v4/group_open_http_svc/delete_group_member" || paths[1] != "/v4/group_open_http_svc/send_group_msg" {
t.Fatalf("kick outbox should delete member before system message, paths=%+v", paths)
}
if bodies[0]["GroupId"] != "room-kick" || bodies[1]["GroupId"] != "room-kick" {
t.Fatalf("kick outbox must use original room group id, bodies=%+v", bodies)
}
}
func TestTencentIMPublisherEnsuresRoomGroupAndRetriesWhenGroupMissing(t *testing.T) {
paths := make([]string, 0, 3)
bodies := make([]map[string]any, 0, 3)
client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
SDKAppID: 1400000000,
SecretKey: "secret",
AdminIdentifier: "administrator",
AdminUserSigTTL: time.Hour,
HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
paths = append(paths, request.URL.Path)
var body map[string]any
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
t.Fatalf("decode request body failed: %v", err)
}
bodies = append(bodies, body)
if len(paths) == 1 {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"FAIL","ErrorCode":10015,"ErrorInfo":"bytes_group_id invalid"}`))),
Header: make(http.Header),
}, nil
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))),
Header: make(http.Header),
}, nil
})},
})
if err != nil {
t.Fatalf("NewRESTClient failed: %v", err)
}
publisher := NewTencentIMPublisher(client)
err = publisher.PublishRoomEvent(context.Background(), tencentim.RoomEvent{
EventID: "evt-retry",
RoomID: "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca",
EventType: "room_mic_changed",
})
if err != nil {
t.Fatalf("PublishRoomEvent should retry after ensuring group: %v", err)
}
wantPaths := []string{
"/v4/group_open_http_svc/send_group_msg",
"/v4/group_open_http_svc/create_group",
"/v4/group_open_http_svc/send_group_msg",
}
if len(paths) != len(wantPaths) {
t.Fatalf("path count mismatch: paths=%+v bodies=%+v", paths, bodies)
}
for index, want := range wantPaths {
if paths[index] != want {
t.Fatalf("path[%d] mismatch: got %s want %s", index, paths[index], want)
}
}
for index, body := range bodies {
if body["GroupId"] != "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca" {
t.Fatalf("request %d must preserve original room group id: %+v", index, body)
}
}
}
type roundTripFunc func(*http.Request) (*http.Response, error)

View File

@ -64,6 +64,8 @@ type CreateRoom struct {
Mode string `json:"mode"`
// VisibleRegionID 是创建时固定的房间列表区域0 表示 GLOBAL。
VisibleRegionID int64 `json:"visible_region_id"`
// OwnerCountryCode 是创建瞬间房主所属国家,列表国家筛选以该投影为准。
OwnerCountryCode string `json:"owner_country_code,omitempty"`
// RoomName 是房间展示名称,恢复时回填 RoomExt["title"],不拆成独立状态 owner。
RoomName string `json:"room_name"`
// RoomAvatar 是创建时必填的房间封面,恢复时回填 RoomExt["cover_url"]。
@ -161,15 +163,16 @@ func (CloseRoom) Type() string { return "close_room" }
// AdminUpdateRoom 定义后台房间资料和生命周期修改请求。
type AdminUpdateRoom struct {
Base
Title *string `json:"title,omitempty"`
CoverURL *string `json:"cover_url,omitempty"`
Description *string `json:"description,omitempty"`
Mode *string `json:"mode,omitempty"`
Status *string `json:"status,omitempty"`
VisibleRegionID *int64 `json:"visible_region_id,omitempty"`
CloseReason string `json:"close_reason,omitempty"`
AdminID uint64 `json:"admin_id,omitempty"`
AdminName string `json:"admin_name,omitempty"`
Title *string `json:"title,omitempty"`
CoverURL *string `json:"cover_url,omitempty"`
Description *string `json:"description,omitempty"`
Mode *string `json:"mode,omitempty"`
Status *string `json:"status,omitempty"`
VisibleRegionID *int64 `json:"visible_region_id,omitempty"`
OwnerCountryCode *string `json:"owner_country_code,omitempty"`
CloseReason string `json:"close_reason,omitempty"`
AdminID uint64 `json:"admin_id,omitempty"`
AdminName string `json:"admin_name,omitempty"`
}
func (AdminUpdateRoom) Type() string { return "admin_update_room" }
@ -416,6 +419,8 @@ type SendGift struct {
GiftPointAdded int64 `json:"gift_point_added,omitempty"`
// HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。
HeatValue int64 `json:"heat_value,omitempty"`
// TargetGiftValues 记录每个收礼用户本次从 wallet receipt 得到的热度增量;恢复时只按目标增量重建收礼用户在线态。
TargetGiftValues map[int64]int64 `json:"target_gift_values,omitempty"`
// PriceVersion 是本次钱包结算使用的服务端礼物价格版本。
PriceVersion string `json:"price_version,omitempty"`
// GiftTypeCode 是 wallet-service 结算时锁定的礼物类型,用于火箭礼物类型燃料规则。

View File

@ -243,6 +243,14 @@ func applyAdminRoomPatch(current *state.RoomState, cmd command.AdminUpdateRoom)
current.VisibleRegionID = *cmd.VisibleRegionID
changed = true
}
if cmd.OwnerCountryCode != nil {
// 房主国家只作为发现页分类投影字段保存在 RoomExt后台 HTTP 不开放直改入口,只有 user-service 国家事实同步会写入。
ownerCountryCode := normalizeRoomCountryCode(*cmd.OwnerCountryCode)
if current.RoomExt[roomExtOwnerCountryKey] != ownerCountryCode {
current.RoomExt[roomExtOwnerCountryKey] = ownerCountryCode
changed = true
}
}
if cmd.Status != nil && current.Status != *cmd.Status {
current.Status = *cmd.Status
changed = true

View File

@ -21,11 +21,12 @@ import (
)
const (
roomExtTitleKey = "title"
roomExtCoverURLKey = "cover_url"
roomExtDescriptionKey = "description"
roomExtShortIDKey = "room_short_id"
roomExtBackgroundKey = "background_url"
roomExtTitleKey = "title"
roomExtCoverURLKey = "cover_url"
roomExtDescriptionKey = "description"
roomExtShortIDKey = "room_short_id"
roomExtOwnerCountryKey = "owner_country_code"
roomExtBackgroundKey = "background_url"
// defaultRoomAvatar 只服务房间资料更新/后台清空封面后的兜底;创建房间必须显式提交封面。
defaultRoomAvatar = "hyapp://room/default-avatar"
@ -39,10 +40,11 @@ const (
// roomProfileInput 是创建房间时需要落到 RoomExt 的展示资料集合。
type roomProfileInput struct {
Name string
Avatar string
Description string
ShortID string
Name string
Avatar string
Description string
ShortID string
OwnerCountryCode string
}
// CreateRoom 创建一个新的房间执行单元,并把首个快照和事件落盘。
@ -92,15 +94,16 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
// protobuf 请求先收敛为 command后续幂等、持久化和恢复都只认识 command 模型。
cmd := command.CreateRoom{
Base: baseFromMeta(req.GetMeta()),
OwnerUserID: actorUserID,
SeatCount: seatCount,
Mode: mode,
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
RoomName: profile.Name,
RoomAvatar: profile.Avatar,
RoomDescription: profile.Description,
RoomShortID: normalizeRoomShortID(req.GetRoomShortId(), actorUserID),
Base: baseFromMeta(req.GetMeta()),
OwnerUserID: actorUserID,
SeatCount: seatCount,
Mode: mode,
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
OwnerCountryCode: normalizeRoomCountryCode(req.GetOwnerCountryCode()),
RoomName: profile.Name,
RoomAvatar: profile.Avatar,
RoomDescription: profile.Description,
RoomShortID: normalizeRoomShortID(req.GetRoomShortId(), actorUserID),
}
idempotencyStartedAt := time.Now()
@ -174,10 +177,11 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
roomState.Version = 1
roomState.VisibleRegionID = cmd.VisibleRegionID
applyRoomProfileExt(roomState, roomProfileInput{
Name: cmd.RoomName,
Avatar: cmd.RoomAvatar,
Description: cmd.RoomDescription,
ShortID: cmd.RoomShortID,
Name: cmd.RoomName,
Avatar: cmd.RoomAvatar,
Description: cmd.RoomDescription,
ShortID: cmd.RoomShortID,
OwnerCountryCode: cmd.OwnerCountryCode,
})
roomState.OnlineUsers[cmd.ActorUserID()] = &state.RoomUserState{
UserID: cmd.ActorUserID(),
@ -332,4 +336,5 @@ func applyRoomProfileExt(roomState *state.RoomState, profile roomProfileInput) {
roomState.RoomExt[roomExtCoverURLKey] = profile.Avatar
roomState.RoomExt[roomExtDescriptionKey] = profile.Description
roomState.RoomExt[roomExtShortIDKey] = profile.ShortID
roomState.RoomExt[roomExtOwnerCountryKey] = normalizeRoomCountryCode(profile.OwnerCountryCode)
}

View File

@ -153,6 +153,26 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
// 扣费成功后Room Cell 同步更新热度和本地礼物榜。
current.Heat += heatValue
current.GiftRank = localRank.ApplyGift(cmd.ActorUserID(), heatValue, now)
targetGiftValueDeltas := make(map[int64]int64, len(targetBillings))
targetCurrentGiftValues := make(map[int64]int64, len(targetBillings))
for _, targetBilling := range targetBillings {
if targetBilling.Billing == nil || targetBilling.TargetUserID <= 0 {
continue
}
targetGiftValue := targetBilling.Billing.GetHeatValue()
if userState := current.OnlineUsers[targetBilling.TargetUserID]; userState != nil {
// 收礼用户热度完全使用 wallet-service 已结算的 HeatValueroom-service 不重新判断普通/幸运/超级幸运比例。
if targetGiftValue > 0 {
userState.GiftValue += targetGiftValue
targetGiftValueDeltas[targetBilling.TargetUserID] += targetGiftValue
}
targetCurrentGiftValues[targetBilling.TargetUserID] = userState.GiftValue
}
}
if len(targetGiftValueDeltas) > 0 {
// 命令日志只保存每个目标本次增量;累计值由在线用户态按 replay 顺序重建。
settledCommand.TargetGiftValues = targetGiftValueDeltas
}
current.Version++
rocketApply, err := s.applyRoomRocketGift(now, current, rocketConfig, cmd, &settledCommand, billing, roomMeta)
if err != nil {
@ -184,6 +204,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
GiftName: targetBilling.Billing.GetGiftName(),
GiftIconUrl: targetBilling.Billing.GetGiftIconUrl(),
GiftAnimationUrl: targetBilling.Billing.GetGiftAnimationUrl(),
TargetGiftValue: targetCurrentGiftValues[targetBilling.TargetUserID],
})
if err != nil {
return mutationResult{}, nil, err
@ -277,6 +298,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
"gift_name": billing.GetGiftName(),
"gift_icon_url": billing.GetGiftIconUrl(),
"gift_animation_url": billing.GetGiftAnimationUrl(),
"target_gift_value": fmt.Sprintf("%d", targetCurrentGiftValues[cmd.TargetUserID]),
},
},
}, records, nil

View File

@ -36,6 +36,7 @@ const (
type roomListCursor struct {
Tab string `json:"tab"`
Query string `json:"query,omitempty"`
CountryCode string `json:"country_code,omitempty"`
SortScore int64 `json:"sort_score,omitempty"`
CreatedAtMS int64 `json:"created_at_ms,omitempty"`
UpdatedAtMS int64 `json:"updated_at_ms,omitempty"`
@ -66,7 +67,8 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (
if err != nil {
return nil, err
}
cursor, err := decodeRoomListCursor(tab, query, req.GetCursor())
countryCode := normalizeRoomCountryCode(req.GetCountryCode())
cursor, err := decodeRoomListCursor(tab, query, countryCode, req.GetCursor())
if err != nil {
return nil, err
}
@ -76,6 +78,7 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
Tab: tab,
Query: query,
CountryCode: countryCode,
Limit: limit + 1,
CursorSortScore: cursor.SortScore,
CursorCreatedAtMS: cursor.CreatedAtMS,
@ -90,7 +93,7 @@ func (s *Service) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (
return nil, err
}
return roomListResponseFromEntries(tab, query, entries, limit), nil
return roomListResponseFromEntries(tab, query, countryCode, entries, limit), nil
}
// ListRoomFeeds 查询 Mine 页 visited/friend/following/followed 房间流。
@ -112,7 +115,7 @@ func (s *Service) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRe
if err != nil {
return nil, err
}
cursor, err := decodeRoomListCursor(tab, query, req.GetCursor())
cursor, err := decodeRoomListCursor(tab, query, "", req.GetCursor())
if err != nil {
return nil, err
}
@ -160,7 +163,7 @@ func (s *Service) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRe
return nil, err
}
return roomListResponseFromEntries(tab, query, entries, limit), nil
return roomListResponseFromEntries(tab, query, "", entries, limit), nil
}
// GetMyRoom 查询当前用户自己创建的房间,不依赖 room_list_entries 投影命中。
@ -193,12 +196,12 @@ func (s *Service) GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (
}, nil
}
func roomListResponseFromEntries(tab string, query string, entries []RoomListEntry, limit int) *roomv1.ListRoomsResponse {
func roomListResponseFromEntries(tab string, query string, countryCode string, entries []RoomListEntry, limit int) *roomv1.ListRoomsResponse {
nextCursor := ""
if len(entries) > limit {
// 多查一条用于判断是否存在下一页;返回数据只包含请求的 limit 条。
last := entries[limit-1]
nextCursor = encodeRoomListCursor(tab, query, last)
nextCursor = encodeRoomListCursor(tab, query, countryCode, last)
entries = entries[:limit]
}
@ -280,6 +283,7 @@ func roomListEntryFromSnapshot(snapshot *roomv1.RoomSnapshot, visibleRegionID in
RoomID: snapshot.GetRoomId(),
RoomShortID: roomShortID,
VisibleRegionID: normalizeVisibleRegionID(visibleRegionID),
OwnerCountryCode: normalizeRoomCountryCode(snapshot.GetRoomExt()[roomExtOwnerCountryKey]),
OwnerUserID: snapshot.GetOwnerUserId(),
Title: title,
CoverURL: coverURL,
@ -387,11 +391,25 @@ func normalizeVisibleRegionID(regionID int64) int64 {
return regionID
}
// normalizeRoomCountryCode 把国家码收敛为大写字母;非法值按空值处理,避免污染 SQL 精确筛选。
func normalizeRoomCountryCode(countryCode string) string {
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
if len(countryCode) < 2 || len(countryCode) > 3 {
return ""
}
for _, ch := range countryCode {
if ch < 'A' || ch > 'Z' {
return ""
}
}
return countryCode
}
// decodeRoomListCursor 校验不透明 cursor 和当前 tab 一致,禁止客户端跨排序维度复用游标。
func decodeRoomListCursor(tab string, query string, encoded string) (roomListCursor, error) {
func decodeRoomListCursor(tab string, query string, countryCode string, encoded string) (roomListCursor, error) {
encoded = strings.TrimSpace(encoded)
if encoded == "" {
return roomListCursor{Tab: tab, Query: query}, nil
return roomListCursor{Tab: tab, Query: query, CountryCode: countryCode}, nil
}
payload, err := base64.RawURLEncoding.DecodeString(encoded)
@ -403,7 +421,7 @@ func decodeRoomListCursor(tab string, query string, encoded string) (roomListCur
if err := json.Unmarshal(payload, &cursor); err != nil {
return roomListCursor{}, xerr.New(xerr.InvalidArgument, "cursor is invalid")
}
if cursor.Tab != tab || cursor.Query != query || cursor.RoomID == "" {
if cursor.Tab != tab || cursor.Query != query || cursor.CountryCode != countryCode || cursor.RoomID == "" {
return roomListCursor{}, xerr.New(xerr.InvalidArgument, "cursor is invalid")
}
@ -412,10 +430,11 @@ func decodeRoomListCursor(tab string, query string, encoded string) (roomListCur
// encodeRoomListCursor 把最后一条列表记录编码成下一页边界。
// cursor 不暴露契约语义,后续调整排序字段时可以只兼容服务端解析逻辑。
func encodeRoomListCursor(tab string, query string, entry RoomListEntry) string {
func encodeRoomListCursor(tab string, query string, countryCode string, entry RoomListEntry) string {
cursor := roomListCursor{
Tab: tab,
Query: query,
CountryCode: countryCode,
SortScore: entry.SortScore,
CreatedAtMS: entry.CreatedAtMS,
UpdatedAtMS: entry.UpdatedAtMS,
@ -453,6 +472,7 @@ func roomListItemToProto(entry RoomListEntry) *roomv1.RoomListItem {
SeatCount: entry.SeatCount,
OccupiedSeatCount: entry.OccupiedSeatCount,
VisibleRegionId: entry.VisibleRegionID,
CountryCode: normalizeRoomCountryCode(entry.OwnerCountryCode),
}
}
@ -460,17 +480,18 @@ func roomListEntryFromMetaAndSnapshot(meta RoomMeta, snapshot *roomv1.RoomSnapsh
if snapshot == nil || snapshot.GetRoomId() == "" {
// GetMyRoom 兜底路径:即使快照暂时不可用,也能用 rooms 元数据返回“有房间”的最小卡片。
return RoomListEntry{
AppCode: meta.AppCode,
RoomID: meta.RoomID,
RoomShortID: meta.RoomShortID,
VisibleRegionID: normalizeVisibleRegionID(meta.VisibleRegionID),
OwnerUserID: meta.OwnerUserID,
Mode: meta.Mode,
Status: meta.Status,
Locked: meta.RoomPasswordHash != "",
SeatCount: meta.SeatCount,
CreatedAtMS: nowMS,
UpdatedAtMS: nowMS,
AppCode: meta.AppCode,
RoomID: meta.RoomID,
RoomShortID: meta.RoomShortID,
VisibleRegionID: normalizeVisibleRegionID(meta.VisibleRegionID),
OwnerCountryCode: "",
OwnerUserID: meta.OwnerUserID,
Mode: meta.Mode,
Status: meta.Status,
Locked: meta.RoomPasswordHash != "",
SeatCount: meta.SeatCount,
CreatedAtMS: nowMS,
UpdatedAtMS: nowMS,
}
}

View File

@ -71,6 +71,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
current.MicSeats[index].MicHeartbeatAtMS = 0
current.MicSeats[index].MicMuted = false
seatStatus := current.MicSeats[index].SeatStatus()
targetGiftValue := roomUserGiftValue(current, cmd.ActorUserID())
// RoomMicChanged 是房间外消费者和腾讯云 IM 共同使用的麦位事件源。
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
@ -83,6 +84,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
PublishState: state.MicPublishPending,
PublishDeadlineMs: cmd.PublishDeadlineMS,
SeatStatus: seatStatus,
TargetGiftValue: targetGiftValue,
})
if err != nil {
return mutationResult{}, nil, err
@ -107,6 +109,7 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.
"mic_session_id": cmd.MicSessionID,
"publish_deadline_ms": int64String(cmd.PublishDeadlineMS),
"seat_status": seatStatus,
"target_gift_value": int64String(targetGiftValue),
},
},
}, []outbox.Record{micEvent}, nil
@ -296,6 +299,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
current.MicSeats[toIndex].MicMuted = movedSeat.MicMuted
current.Version++
seatStatus := current.MicSeats[toIndex].SeatStatus()
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
ActorUserId: cmd.ActorUserID(),
@ -307,6 +311,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
PublishState: movedSeat.PublishState,
PublishDeadlineMs: movedSeat.PublishDeadlineMS,
SeatStatus: seatStatus,
TargetGiftValue: targetGiftValue,
})
if err != nil {
return mutationResult{}, nil, err
@ -328,6 +333,7 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe
"mic_session_id": movedSeat.MicSessionID,
"publish_deadline_ms": int64String(movedSeat.PublishDeadlineMS),
"seat_status": seatStatus,
"target_gift_value": int64String(targetGiftValue),
},
},
}, []outbox.Record{micEvent}, nil
@ -401,6 +407,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
}
current.Version++
seatStatus := current.MicSeats[index].SeatStatus()
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{
ActorUserId: cmd.ActorUserID(),
@ -414,6 +421,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
PublishEventTimeMs: cmd.EventTimeMS,
MicMuted: current.MicSeats[index].MicMuted,
SeatStatus: seatStatus,
TargetGiftValue: targetGiftValue,
})
if err != nil {
return mutationResult{}, nil, err
@ -441,6 +449,7 @@ func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmM
"source": cmd.Source,
"mic_muted": boolString(current.MicSeats[index].MicMuted),
"seat_status": seatStatus,
"target_gift_value": int64String(targetGiftValue),
},
},
}, []outbox.Record{micEvent}, nil
@ -565,6 +574,7 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
current.MicSeats[index].MicMuted = cmd.Muted
current.Version++
seatStatus := current.MicSeats[index].SeatStatus()
targetGiftValue := roomUserGiftValue(current, cmd.TargetUserID)
action := "mic_unmute"
if cmd.Muted {
@ -581,6 +591,7 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
PublishDeadlineMs: seat.PublishDeadlineMS,
MicMuted: cmd.Muted,
SeatStatus: seatStatus,
TargetGiftValue: targetGiftValue,
})
if err != nil {
return mutationResult{}, nil, err
@ -598,11 +609,12 @@ func (s *Service) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest)
SeatNo: seat.SeatNo,
RoomVersion: current.Version,
Attributes: map[string]string{
"action": action,
"mic_session_id": seat.MicSessionID,
"publish_state": seat.PublishState,
"mic_muted": boolString(cmd.Muted),
"seat_status": seatStatus,
"action": action,
"mic_session_id": seat.MicSessionID,
"publish_state": seat.PublishState,
"mic_muted": boolString(cmd.Muted),
"seat_status": seatStatus,
"target_gift_value": int64String(targetGiftValue),
},
},
}, []outbox.Record{micEvent}, nil
@ -654,3 +666,14 @@ func protoSeatByUser(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatSt
return nil
}
func roomUserGiftValue(current *state.RoomState, userID int64) int64 {
if current == nil || userID <= 0 {
return 0
}
if userState := current.OnlineUsers[userID]; userState != nil {
// 麦位事件只透出在线用户态中的收礼热度,避免把麦位号当成热度归属。
return userState.GiftValue
}
return 0
}

View File

@ -148,17 +148,86 @@ func TestPinnedRoomListOrdersGlobalRegionLocalThenOtherForNew(t *testing.T) {
}
}
func createPinnedListRoom(t *testing.T, ctx context.Context, svc *roomservice.Service, roomID string, shortID string, ownerID int64, regionID int64) {
func TestRoomListFiltersByOwnerCountry(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
svc := roomservice.New(roomservice.Config{
NodeID: "node-country-filter-test",
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 1,
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
createPinnedListRoom(t, ctx, svc, "room-us-low", "us-low", 3301, 9001, "US")
createPinnedListRoom(t, ctx, svc, "room-ae", "ae", 3302, 9001, "AE")
createPinnedListRoom(t, ctx, svc, "room-us-high", "us-high", 3303, 9001, "US")
repository.SetRoomSortScore("room-us-low", 100)
repository.SetRoomSortScore("room-ae", 200)
repository.SetRoomSortScore("room-us-high", 300)
firstPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 4001,
VisibleRegionId: 9001,
Tab: "hot",
Limit: 1,
CountryCode: "us",
})
if err != nil {
t.Fatalf("list country filtered first page failed: %v", err)
}
if len(firstPage.GetRooms()) != 1 || firstPage.GetRooms()[0].GetRoomId() != "room-us-high" || firstPage.GetRooms()[0].GetCountryCode() != "US" {
t.Fatalf("country filter must return only matching owner country in hot order: %+v", firstPage.GetRooms())
}
if firstPage.GetNextCursor() == "" {
t.Fatalf("first country page should return cursor")
}
if _, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 4001,
VisibleRegionId: 9001,
Tab: "hot",
Limit: 1,
CountryCode: "AE",
Cursor: firstPage.GetNextCursor(),
}); err == nil {
t.Fatalf("country cursor must not be reusable across country tabs")
}
secondPage, err := svc.ListRooms(ctx, &roomv1.ListRoomsRequest{
Meta: &roomv1.RequestMeta{AppCode: appcode.Default},
ViewerUserId: 4001,
VisibleRegionId: 9001,
Tab: "hot",
Limit: 1,
CountryCode: "US",
Cursor: firstPage.GetNextCursor(),
})
if err != nil {
t.Fatalf("list country filtered second page failed: %v", err)
}
if len(secondPage.GetRooms()) != 1 || secondPage.GetRooms()[0].GetRoomId() != "room-us-low" || secondPage.GetRooms()[0].GetCountryCode() != "US" {
t.Fatalf("second country page mismatch: %+v", secondPage.GetRooms())
}
}
func createPinnedListRoom(t *testing.T, ctx context.Context, svc *roomservice.Service, roomID string, shortID string, ownerID int64, regionID int64, countryCodes ...string) {
t.Helper()
ownerCountryCode := ""
if len(countryCodes) > 0 {
ownerCountryCode = countryCodes[0]
}
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
Meta: roomservice.NewRequestMeta(roomID, ownerID),
SeatCount: 10,
Mode: "voice",
VisibleRegionId: regionID,
RoomName: roomID,
RoomAvatar: testRoomCoverURL,
RoomShortId: shortID,
Meta: roomservice.NewRequestMeta(roomID, ownerID),
SeatCount: 10,
Mode: "voice",
VisibleRegionId: regionID,
OwnerCountryCode: ownerCountryCode,
RoomName: roomID,
RoomAvatar: testRoomCoverURL,
RoomShortId: shortID,
}); err != nil {
t.Fatalf("create room %s failed: %v", roomID, err)
}

View File

@ -379,6 +379,15 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64)
// 送礼回放不能再次调用 wallet只使用命令中记录的钱包结算热度重建展示态。
total := typed.HeatValue
current.Heat += total
for targetUserID, giftValue := range sendGiftTargetGiftValues(typed) {
if giftValue <= 0 {
continue
}
if userState := current.OnlineUsers[targetUserID]; userState != nil {
// 收礼用户热度只恢复当时仍在房间内的在线态;后续 Leave/Kick/Stale 命令会删除在线态并自然清零。
userState.GiftValue += giftValue
}
}
applied := false
for index := range current.GiftRank {
if current.GiftRank[index].UserID == typed.ActorUserID() {
@ -417,6 +426,21 @@ func replay(current *state.RoomState, cmd command.Command, committedAtMS int64)
return nil
}
func sendGiftTargetGiftValues(cmd *command.SendGift) map[int64]int64 {
if cmd == nil {
return nil
}
if len(cmd.TargetGiftValues) > 0 {
return cmd.TargetGiftValues
}
if cmd.TargetUserID > 0 && len(cmd.TargetUserIDs) <= 1 && cmd.HeatValue > 0 {
// 旧单目标命令没有 TargetGiftValues 时HeatValue 同时就是该唯一收礼人的本次热度增量。
return map[int64]int64{cmd.TargetUserID: cmd.HeatValue}
}
// 旧多目标命令无法从聚合 HeatValue 反推出每个收礼人的值,宁可不恢复也不能把总值错误扩散给每个目标。
return nil
}
func rocketStateFromSettledGift(cmd *command.SendGift) *state.RocketState {
return &state.RocketState{
CurrentLevel: cmd.RocketLevel,

View File

@ -128,6 +128,8 @@ type RoomListEntry struct {
RoomShortID string
// VisibleRegionID 是房间自身的列表区域;公共发现列表会用它把当前区域房间排在其他区域之前。
VisibleRegionID int64
// OwnerCountryCode 是房主国家码;公共发现列表国家 tab 使用它做服务端筛选。
OwnerCountryCode string
// OwnerUserID 是房间所有者,用于卡片展示和运营排查。
OwnerUserID int64
// Title 是卡片标题;空值由客户端或后续资料聚合兜底。
@ -364,6 +366,8 @@ type RoomListQuery struct {
OwnerUserID int64
// Query 非空时按 room_id 或 title 做包含匹配。
Query string
// CountryCode 非空时只返回房主国家命中的房间gateway 必须先校验它属于当前用户区域。
CountryCode string
// Limit 是实际查询数量service 层会限制最大值。
Limit int
// CursorSortScore 是 hot tab 的游标 score。

View File

@ -129,6 +129,12 @@ func TestSendGiftBatchDebitsMultipleTargets(t *testing.T) {
if resp.GetRoomHeat() != 30 || resp.GetBillingReceiptId() != "receipt-202,receipt-303" {
t.Fatalf("multi-target gift response must aggregate wallet results: %+v", resp)
}
if firstUser := onlineUserByID(resp.GetRoom(), firstTargetID); firstUser == nil || firstUser.GetGiftValue() != 11 {
t.Fatalf("first target must receive only its own heat value: %+v", firstUser)
}
if secondUser := onlineUserByID(resp.GetRoom(), secondTargetID); secondUser == nil || secondUser.GetGiftValue() != 19 {
t.Fatalf("second target must receive only its own heat value: %+v", secondUser)
}
if len(wallet.debitRequests) != 0 {
t.Fatalf("multi-target gift must use wallet BatchDebitGift, single debits=%+v", wallet.debitRequests)
}
@ -150,9 +156,15 @@ func TestSendGiftBatchDebitsMultipleTargets(t *testing.T) {
if got[firstTargetID].GetBillingReceiptId() != "receipt-202" || got[firstTargetID].GetGiftValue() != 11 || got[firstTargetID].GetCommandId() != "cmd-gift-multi:target:202" {
t.Fatalf("first target GiftSent mismatch: %+v", got[firstTargetID])
}
if got[firstTargetID].GetTargetGiftValue() != 11 {
t.Fatalf("first target GiftSent must carry its cumulative value: %+v", got[firstTargetID])
}
if got[secondTargetID].GetBillingReceiptId() != "receipt-303" || got[secondTargetID].GetGiftValue() != 19 || got[secondTargetID].GetCommandId() != "cmd-gift-multi:target:303" {
t.Fatalf("second target GiftSent mismatch: %+v", got[secondTargetID])
}
if got[secondTargetID].GetTargetGiftValue() != 19 {
t.Fatalf("second target GiftSent must carry its cumulative value: %+v", got[secondTargetID])
}
}
func (w *rocketTestWallet) GrantResourceGroup(_ context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error) {

View File

@ -0,0 +1,192 @@
package service_test
import (
"context"
"testing"
"time"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/services/room-service/internal/integration"
roomservice "hyapp/services/room-service/internal/room/service"
"hyapp/services/room-service/internal/router"
"hyapp/services/room-service/internal/testutil/mysqltest"
)
func TestRoomUserGiftValueFollowsMicChangeAndResetsAfterLeave(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 0, 0, 0, time.UTC)}
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
BillingReceiptId: "receipt-target-heat",
CoinSpent: 1000,
ChargeAmount: 1000,
HeatValue: 117,
GiftTypeCode: "normal",
}}}
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-value")
roomID := "room-user-gift-value"
ownerID := int64(61001)
targetID := int64(61002)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
joinRocketRoom(t, ctx, svc, roomID, targetID)
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
Meta: rocketMeta(roomID, targetID, "target-heat-up"),
SeatNo: 2,
}); err != nil {
t.Fatalf("target mic up failed: %v", err)
}
giftResp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
Meta: rocketMeta(roomID, ownerID, "target-heat-gift"),
TargetType: "user",
TargetUserId: targetID,
GiftId: "gift-target-heat",
GiftCount: 1,
})
if err != nil {
t.Fatalf("send gift failed: %v", err)
}
if user := onlineUserByID(giftResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 117 {
t.Fatalf("target online user gift_value mismatch: %+v", user)
}
if seat := seatByNo(giftResp.GetRoom(), 2); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 117 {
t.Fatalf("target seat gift_value mismatch after gift: %+v", seat)
}
giftEvents := roomGiftSentEvents(t, ctx, repository)
if len(giftEvents) != 1 || giftEvents[0].GetTargetGiftValue() != 117 {
t.Fatalf("RoomGiftSent must carry target cumulative gift value: %+v", giftEvents)
}
changeResp, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
Meta: rocketMeta(roomID, targetID, "target-heat-change"),
TargetUserId: targetID,
SeatNo: 5,
})
if err != nil {
t.Fatalf("change mic failed: %v", err)
}
if oldSeat := seatByNo(changeResp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
t.Fatalf("old seat must be empty after change: %+v", oldSeat)
}
if newSeat := seatByNo(changeResp.GetRoom(), 5); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 117 {
t.Fatalf("gift value must follow user to new seat: %+v", newSeat)
}
if changed := firstMicChangedEventByAction(t, ctx, repository, "change"); changed == nil || changed.GetTargetGiftValue() != 117 {
t.Fatalf("RoomMicChanged/change must carry target gift value: %+v", changed)
}
if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: rocketMeta(roomID, targetID, "target-heat-leave")}); err != nil {
t.Fatalf("leave room failed: %v", err)
}
joinRocketRoom(t, ctx, svc, roomID, targetID)
reupResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
Meta: rocketMeta(roomID, targetID, "target-heat-reup"),
SeatNo: 1,
})
if err != nil {
t.Fatalf("target mic re-up failed: %v", err)
}
if user := onlineUserByID(reupResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 0 {
t.Fatalf("gift value must reset after leave and rejoin: %+v", user)
}
if seat := seatByNo(reupResp.GetRoom(), 1); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 0 {
t.Fatalf("rejoined user seat gift_value must be zero: %+v", seat)
}
}
func TestRoomUserGiftValueRecoversFromCommandLog(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 30, 0, 0, time.UTC)}
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
BillingReceiptId: "receipt-replay-target-heat",
CoinSpent: 1500,
ChargeAmount: 1500,
HeatValue: 367,
GiftTypeCode: "lucky",
}}}
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-replay-writer")
roomID := "room-user-gift-replay"
ownerID := int64(62001)
targetID := int64(62002)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
joinRocketRoom(t, ctx, svc, roomID, targetID)
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
Meta: rocketMeta(roomID, targetID, "replay-up"),
SeatNo: 2,
}); err != nil {
t.Fatalf("target mic up failed: %v", err)
}
if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
Meta: rocketMeta(roomID, ownerID, "replay-gift"),
TargetType: "user",
TargetUserId: targetID,
GiftId: "gift-replay-target-heat",
GiftCount: 1,
}); err != nil {
t.Fatalf("send gift failed: %v", err)
}
if _, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
Meta: rocketMeta(roomID, targetID, "replay-change"),
TargetUserId: targetID,
SeatNo: 4,
}); err != nil {
t.Fatalf("change mic failed: %v", err)
}
recoveredSvc := newUserGiftValueTestService(repository, &rocketTestWallet{}, now, "node-user-gift-replay-reader")
resp, err := recoveredSvc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
Meta: rocketMeta(roomID, ownerID, "replay-snapshot"),
RoomId: roomID,
ViewerUserId: ownerID,
})
if err != nil {
t.Fatalf("recover room snapshot failed: %v", err)
}
if user := onlineUserByID(resp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 367 {
t.Fatalf("recovered online user gift_value mismatch: %+v", user)
}
if oldSeat := seatByNo(resp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
t.Fatalf("recovered old seat must stay empty: %+v", oldSeat)
}
if newSeat := seatByNo(resp.GetRoom(), 4); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 367 {
t.Fatalf("recovered seat must keep target gift value after change: %+v", newSeat)
}
}
func newUserGiftValueTestService(repository *mysqltest.Repository, wallet *rocketTestWallet, clock *fixedRoomRocketClock, nodeID string) *roomservice.Service {
return roomservice.New(roomservice.Config{
NodeID: nodeID,
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 100,
Clock: clock,
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
}
func firstMicChangedEventByAction(t *testing.T, ctx context.Context, repository *mysqltest.Repository, action string) *roomeventsv1.RoomMicChanged {
t.Helper()
records, err := repository.ListPendingOutbox(ctx, 100)
if err != nil {
t.Fatalf("list pending outbox failed: %v", err)
}
for _, record := range records {
if record.EventType != "RoomMicChanged" {
continue
}
var event roomeventsv1.RoomMicChanged
if err := proto.Unmarshal(record.Envelope.GetBody(), &event); err != nil {
t.Fatalf("decode mic changed event failed: %v", err)
}
if event.GetAction() == action {
return &event
}
}
return nil
}

View File

@ -9,6 +9,7 @@ import (
roomv1 "hyapp.local/api/proto/room/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/usermq"
"hyapp/services/room-service/internal/room/command"
"hyapp/services/room-service/internal/room/state"
)
@ -46,10 +47,14 @@ func (s *Service) HandleUserOutboxMessage(ctx context.Context, message usermq.Us
if err != nil {
return err
}
if !exists || meta.Status == state.RoomStatusDeleted || meta.VisibleRegionID == payload.NewRegionID {
if !exists || meta.Status == state.RoomStatusDeleted {
return nil
}
newCountry := normalizeRoomCountryCode(payload.NewCountry)
if meta.VisibleRegionID == payload.NewRegionID && newCountry == "" {
return nil
}
requestID := strings.TrimSpace(payload.RequestID)
if requestID == "" {
requestID = message.EventID
@ -58,17 +63,25 @@ func (s *Service) HandleUserOutboxMessage(ctx context.Context, message usermq.Us
if sentAtMS <= 0 {
sentAtMS = message.OccurredAtMS
}
_, err = s.AdminUpdateRoom(ctx, &roomv1.AdminUpdateRoomRequest{
Meta: &roomv1.RequestMeta{
AppCode: message.AppCode,
RequestId: requestID,
CommandId: message.EventID,
ActorUserId: payload.UserID,
RoomId: meta.RoomID,
SentAtMs: sentAtMS,
},
VisibleRegionId: &payload.NewRegionID,
AdminName: "user-region-sync",
})
updateMeta := &roomv1.RequestMeta{
AppCode: message.AppCode,
RequestId: requestID,
CommandId: message.EventID,
ActorUserId: payload.UserID,
RoomId: meta.RoomID,
SentAtMs: sentAtMS,
}
cmd := command.AdminUpdateRoom{
Base: baseFromMeta(updateMeta),
AdminName: "user-region-sync",
}
if meta.VisibleRegionID != payload.NewRegionID {
cmd.VisibleRegionID = &payload.NewRegionID
}
if newCountry != "" {
// UserRegionChanged 同时承载国家事实;即使区域未变,也要把房主国家投影写回房间列表。
cmd.OwnerCountryCode = &newCountry
}
_, err = s.applyAdminRoomMutation(ctx, cmd, cmd)
return err
}

View File

@ -28,13 +28,14 @@ func TestHandleUserRegionChangedUpdatesOwnerRoomRegionIdempotently(t *testing.T)
roomID := "room-user-region-sync"
ownerID := int64(163212)
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
Meta: roomservice.NewRequestMeta(roomID, ownerID),
SeatCount: 10,
Mode: "voice",
VisibleRegionId: 3,
RoomName: "Region Sync Room",
RoomAvatar: testRoomCoverURL,
RoomShortId: "163212",
Meta: roomservice.NewRequestMeta(roomID, ownerID),
SeatCount: 10,
Mode: "voice",
VisibleRegionId: 3,
OwnerCountryCode: "AE",
RoomName: "Region Sync Room",
RoomAvatar: testRoomCoverURL,
RoomShortId: "163212",
}); err != nil {
t.Fatalf("create room failed: %v", err)
}
@ -79,9 +80,16 @@ func TestHandleUserRegionChangedUpdatesOwnerRoomRegionIdempotently(t *testing.T)
if err != nil {
t.Fatalf("list new region entries failed: %v", err)
}
if len(newRegionEntries) != 1 || newRegionEntries[0].RoomID != roomID || newRegionEntries[0].VisibleRegionID != 2 {
if len(newRegionEntries) != 1 || newRegionEntries[0].RoomID != roomID || newRegionEntries[0].VisibleRegionID != 2 || newRegionEntries[0].OwnerCountryCode != "BD" {
t.Fatalf("new region list projection mismatch: %+v", newRegionEntries)
}
countryEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 2, CountryCode: "BD", Tab: "new", Limit: 10})
if err != nil {
t.Fatalf("list new country entries failed: %v", err)
}
if len(countryEntries) != 1 || countryEntries[0].RoomID != roomID || countryEntries[0].OwnerCountryCode != "BD" {
t.Fatalf("new country list projection mismatch: %+v", countryEntries)
}
oldRegionEntries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 3, Tab: "new", Limit: 10})
if err != nil {
t.Fatalf("list old region entries failed: %v", err)

View File

@ -100,6 +100,8 @@ type RoomUserState struct {
JoinedAtMS int64
// LastSeenAtMS 是最后一次房间业务操作时间,不等同于腾讯云 IM 心跳。
LastSeenAtMS int64
// GiftValue 是该用户本次在线停留期间在当前房间收到的礼物热度,离房删除 OnlineUsers 后自然清零。
GiftValue int64
}
// UserModerationState 保存房间内 ban/mute 这类治理状态的时间边界。
@ -387,12 +389,20 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
Role: userState.Role,
JoinedAtMs: userState.JoinedAtMS,
LastSeenAtMs: userState.LastSeenAtMS,
GiftValue: userState.GiftValue,
})
}
seats := make([]*roomv1.SeatState, 0, len(s.MicSeats))
for _, seat := range s.MicSeats {
seatGiftValue := int64(0)
if seat.UserID > 0 {
if userState, exists := s.OnlineUsers[seat.UserID]; exists && userState != nil {
// 麦位展示值从在线用户态派生,切麦只移动 UserID不会重置已收到的礼物热度。
seatGiftValue = userState.GiftValue
}
}
// 麦位顺序沿用内部切片顺序,即初始化的 SeatNo 升序和后续原位修改。
seats = append(seats, &roomv1.SeatState{
SeatNo: seat.SeatNo,
@ -406,6 +416,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
MicMuted: seat.MicMuted,
SeatStatus: seat.SeatStatus(),
MicHeartbeatAtMs: seat.MicHeartbeatAtMS,
GiftValue: seatGiftValue,
})
}
@ -494,6 +505,7 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState {
Role: user.GetRole(),
JoinedAtMS: user.GetJoinedAtMs(),
LastSeenAtMS: user.GetLastSeenAtMs(),
GiftValue: user.GetGiftValue(),
}
}

View File

@ -98,6 +98,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
room_id VARCHAR(64) NOT NULL,
room_short_id VARCHAR(32) NOT NULL DEFAULT '',
visible_region_id BIGINT NOT NULL DEFAULT 0,
owner_country_code VARCHAR(3) NOT NULL DEFAULT '',
owner_user_id BIGINT NOT NULL,
title VARCHAR(128) NOT NULL DEFAULT '',
cover_url VARCHAR(512) NOT NULL DEFAULT '',
@ -119,6 +120,8 @@ func (r *Repository) Migrate(ctx context.Context) error {
KEY idx_room_list_short_id (app_code, room_short_id),
KEY idx_room_list_region_hot (app_code, visible_region_id, status, sort_score DESC, room_id),
KEY idx_room_list_region_new (app_code, visible_region_id, status, created_at_ms DESC, room_id),
KEY idx_room_list_region_country_hot (app_code, visible_region_id, owner_country_code, status, sort_score DESC, room_id),
KEY idx_room_list_region_country_new (app_code, visible_region_id, owner_country_code, status, created_at_ms DESC, room_id),
KEY idx_room_list_owner (app_code, owner_user_id, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS room_user_presence (
@ -334,6 +337,9 @@ func (r *Repository) Migrate(ctx context.Context) error {
if err := r.ensureRoomListLockSchema(ctx); err != nil {
return err
}
if err := r.ensureRoomListCountrySchema(ctx); err != nil {
return err
}
if err := r.ensureRoomPasswordSchema(ctx); err != nil {
return err
}
@ -433,6 +439,44 @@ func (r *Repository) ensureRoomListLockSchema(ctx context.Context) error {
return nil
}
func (r *Repository) ensureRoomListCountrySchema(ctx context.Context) error {
hasColumn, err := r.columnExists(ctx, "room_list_entries", "owner_country_code")
if err != nil {
return err
}
if !hasColumn {
// owner_country_code 是房主国家快照,只服务发现页国家筛选;历史行先为空,后续房间投影刷新会补真实值。
if _, err := r.db.ExecContext(ctx, `ALTER TABLE room_list_entries ADD COLUMN owner_country_code VARCHAR(3) NOT NULL DEFAULT '' AFTER visible_region_id`); err != nil {
return err
}
}
indexes := []struct {
name string
statement string
}{
{
name: "idx_room_list_region_country_hot",
statement: `ALTER TABLE room_list_entries ADD INDEX idx_room_list_region_country_hot (app_code, visible_region_id, owner_country_code, status, sort_score DESC, room_id)`,
},
{
name: "idx_room_list_region_country_new",
statement: `ALTER TABLE room_list_entries ADD INDEX idx_room_list_region_country_new (app_code, visible_region_id, owner_country_code, status, created_at_ms DESC, room_id)`,
},
}
for _, item := range indexes {
hasIndex, err := r.indexExists(ctx, "room_list_entries", item.name)
if err != nil {
return err
}
if !hasIndex {
if _, err := r.db.ExecContext(ctx, item.statement); err != nil {
return err
}
}
}
return nil
}
func (r *Repository) ensureRoomAdminCloseSchema(ctx context.Context) error {
columns := []struct {
table string
@ -1458,12 +1502,13 @@ func (r *Repository) UpsertRoomListEntry(ctx context.Context, entry roomservice.
appCode := normalizedRecordAppCode(ctx, entry.AppCode)
_, err := r.db.ExecContext(ctx,
`INSERT INTO room_list_entries (
app_code, room_id, room_short_id, visible_region_id, owner_user_id, title, cover_url, mode, status, locked,
app_code, room_id, room_short_id, visible_region_id, owner_country_code, owner_user_id, title, cover_url, mode, status, locked,
heat, online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
room_short_id = VALUES(room_short_id),
visible_region_id = VALUES(visible_region_id),
owner_country_code = VALUES(owner_country_code),
owner_user_id = VALUES(owner_user_id),
title = VALUES(title),
cover_url = VALUES(cover_url),
@ -1480,6 +1525,7 @@ func (r *Repository) UpsertRoomListEntry(ctx context.Context, entry roomservice.
entry.RoomID,
entry.RoomShortID,
entry.VisibleRegionID,
entry.OwnerCountryCode,
entry.OwnerUserID,
entry.Title,
entry.CoverURL,
@ -1524,6 +1570,7 @@ func (r *Repository) ListRoomListEntries(ctx context.Context, query roomservice.
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerCountryCode,
&entry.OwnerUserID,
&entry.Title,
&entry.CoverURL,
@ -1571,7 +1618,7 @@ func (r *Repository) ListRoomListEntriesByIDs(ctx context.Context, query roomser
args = append(args, roomID)
}
rows, err := r.db.QueryContext(ctx, `
SELECT app_code, room_id, room_short_id, visible_region_id, owner_user_id, title, cover_url, mode, status, locked,
SELECT app_code, room_id, room_short_id, visible_region_id, owner_country_code, owner_user_id, title, cover_url, mode, status, locked,
heat, online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms
FROM room_list_entries
WHERE app_code = ? AND room_id IN (`+placeholders+`)`, args...)
@ -1587,6 +1634,7 @@ func (r *Repository) ListRoomListEntriesByIDs(ctx context.Context, query roomser
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerCountryCode,
&entry.OwnerUserID,
&entry.Title,
&entry.CoverURL,
@ -1669,6 +1717,7 @@ func (r *Repository) ListRoomUserFeedEntries(ctx context.Context, query roomserv
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerCountryCode,
&entry.OwnerUserID,
&entry.Title,
&entry.CoverURL,
@ -1794,6 +1843,7 @@ func (r *Repository) ListRoomFollowEntries(ctx context.Context, query roomservic
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerCountryCode,
&entry.OwnerUserID,
&entry.Title,
&entry.CoverURL,
@ -1846,6 +1896,7 @@ func (r *Repository) ListRoomRelatedFeedEntries(ctx context.Context, query rooms
&entry.RoomID,
&entry.RoomShortID,
&entry.VisibleRegionID,
&entry.OwnerCountryCode,
&entry.OwnerUserID,
&entry.Title,
&entry.CoverURL,
@ -1889,18 +1940,18 @@ func (r *Repository) ListRoomRelatedFeedEntries(ctx context.Context, query rooms
}
const roomListSelectColumns = `
SELECT app_code, room_id, room_short_id, visible_region_id, owner_user_id, title, cover_url, mode, status, locked,
SELECT app_code, room_id, room_short_id, visible_region_id, owner_country_code, owner_user_id, title, cover_url, mode, status, locked,
heat, online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms
FROM room_list_entries`
const roomUserFeedSelectColumns = `
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_country_code, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
r.heat, r.online_count, r.seat_count, r.occupied_seat_count, r.sort_score, r.created_at_ms, f.updated_at_ms
FROM room_user_feed_entries f
JOIN room_list_entries r ON r.app_code = f.app_code AND r.room_id = f.room_id`
const roomFollowSelectColumns = `
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_country_code, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
r.heat, r.online_count, r.seat_count, r.occupied_seat_count, r.sort_score, r.created_at_ms, f.followed_at_ms
FROM room_follows f
JOIN room_list_entries r ON r.app_code = f.app_code AND r.room_id = f.room_id`
@ -1921,7 +1972,7 @@ func buildRoomListQuerySQL(query roomservice.RoomListQuery) (string, []any) {
pinWeightExpr := "CASE WHEN gp.room_id IS NOT NULL THEN gp.weight WHEN rp.room_id IS NOT NULL THEN rp.weight ELSE 0 END"
pinExpiresExpr := "CASE WHEN gp.room_id IS NOT NULL THEN gp.expires_at_ms WHEN rp.room_id IS NOT NULL THEN rp.expires_at_ms ELSE 0 END"
selectSQL := `
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
SELECT r.app_code, r.room_id, r.room_short_id, r.visible_region_id, r.owner_country_code, r.owner_user_id, r.title, r.cover_url, r.mode, r.status, r.locked,
r.heat, r.online_count, r.seat_count, r.occupied_seat_count, r.sort_score, r.created_at_ms, r.updated_at_ms,
` + pinnedExpr + ` AS is_pinned, ` + pinRankExpr + ` AS pin_list_rank, ` + pinWeightExpr + ` AS pin_weight, ` + pinExpiresExpr + ` AS pinned_until_ms
FROM room_list_entries r
@ -1947,6 +1998,10 @@ func buildRoomListQuerySQL(query roomservice.RoomListQuery) (string, []any) {
where = append(where, "r.owner_user_id = ?")
args = append(args, query.OwnerUserID)
}
if strings.TrimSpace(query.CountryCode) != "" {
where = append(where, "r.owner_country_code = ?")
args = append(args, strings.ToUpper(strings.TrimSpace(query.CountryCode)))
}
if strings.TrimSpace(query.Query) != "" {
where = append(where, "(r.room_id LIKE ? ESCAPE '\\\\' OR r.room_short_id LIKE ? ESCAPE '\\\\' OR r.title LIKE ? ESCAPE '\\\\')")
like := "%" + escapeRoomListLike(query.Query) + "%"

View File

@ -147,6 +147,31 @@ CREATE TABLE IF NOT EXISTS user_status_change_logs (
KEY idx_user_status_logs_request (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户状态变更日志表';
CREATE TABLE IF NOT EXISTS manager_user_blocks (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
block_id VARCHAR(96) NOT NULL COMMENT '经理封禁记录 ID',
command_id VARCHAR(128) NOT NULL COMMENT '幂等命令 ID',
manager_user_id BIGINT NOT NULL COMMENT '经理用户 ID',
target_user_id BIGINT NOT NULL COMMENT '被封禁用户 ID',
target_display_user_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '被封禁用户短 ID 快照',
target_username VARCHAR(128) NOT NULL DEFAULT '' COMMENT '被封禁用户昵称快照',
target_avatar VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '被封禁用户头像快照',
country VARCHAR(16) NOT NULL DEFAULT '' COMMENT '封禁时双方国家码',
target_old_status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '封禁前用户状态',
blocked_until_ms BIGINT NOT NULL COMMENT '计划解封时间UTC epoch ms',
status VARCHAR(32) NOT NULL COMMENT 'active/released/expired',
reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封禁原因',
released_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因',
released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间UTC epoch ms',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, block_id),
UNIQUE KEY uk_manager_user_blocks_command (app_code, command_id),
KEY idx_manager_user_blocks_manager (app_code, manager_user_id, status, created_at_ms),
KEY idx_manager_user_blocks_target (app_code, target_user_id, status),
KEY idx_manager_user_blocks_expire (app_code, status, blocked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经理中心用户封禁记录表';
CREATE TABLE IF NOT EXISTS user_reports (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
report_id VARCHAR(96) NOT NULL COMMENT '举报单业务 ID',
@ -400,6 +425,7 @@ CREATE TABLE IF NOT EXISTS user_cp_relation_configs (
relation_type VARCHAR(32) NOT NULL COMMENT '关系类型cp、brother、sister',
max_count_per_user INT NOT NULL COMMENT '每个用户可拥有的该类型 active 关系数量上限',
application_expire_hours INT NOT NULL COMMENT '申请过期小时数',
breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '用户主动解除该类型关系需要支付的金币数',
status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '业务状态',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
@ -462,6 +488,11 @@ CREATE TABLE IF NOT EXISTS user_cp_relationships (
source_application_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '建立关系的申请 ID',
formed_at_ms BIGINT NOT NULL COMMENT '建立时间UTC epoch ms',
ended_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '解除时间UTC epoch ms',
ended_by_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '主动解除关系的用户 ID',
breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '本次解除实际支付金币数',
breakup_command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除关系命令 ID',
breakup_wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除扣费钱包交易 ID',
breakup_coin_balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '解除扣费后用户金币余额',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, relationship_id),
@ -470,6 +501,26 @@ CREATE TABLE IF NOT EXISTS user_cp_relationships (
KEY idx_user_cp_rel_user_b (app_code, user_b_id, status, relation_type, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系表';
CREATE TABLE IF NOT EXISTS user_cp_relationship_breakups (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
command_id VARCHAR(128) NOT NULL COMMENT '解除关系命令 ID',
relationship_id VARCHAR(128) NOT NULL COMMENT '关系 ID',
requester_user_id BIGINT NOT NULL COMMENT '发起解除的用户 ID',
relation_type VARCHAR(32) NOT NULL COMMENT '关系类型cp、brother、sister',
breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '创建解除占位时读取到的金币费用',
status VARCHAR(32) NOT NULL COMMENT '解除状态pending、confirmed、canceled',
wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除扣费钱包交易 ID',
coin_balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '扣费后金币余额',
reason VARCHAR(256) NOT NULL DEFAULT '' COMMENT '取消原因',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
confirmed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '确认解除时间UTC epoch ms',
canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '取消解除时间UTC epoch ms',
PRIMARY KEY (app_code, command_id),
KEY idx_user_cp_breakup_relationship_status (app_code, relationship_id, status, updated_at_ms),
KEY idx_user_cp_breakup_requester_status (app_code, requester_user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系解除占位表';
CREATE TABLE IF NOT EXISTS user_cp_gift_event_consumption (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
room_event_id VARCHAR(128) NOT NULL COMMENT 'room outbox 事件 ID',
@ -506,6 +557,98 @@ CREATE TABLE IF NOT EXISTS user_outbox (
KEY idx_user_outbox_aggregate (app_code, aggregate_type, aggregate_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户发件箱表';
-- 老测试库可能已经存在 user_outboxCREATE TABLE IF NOT EXISTS 不会补齐后续新增的 MQ 投递字段。
-- 这里用 information_schema 生成幂等 ALTER保证重复执行 initdb 时不会因重复列名失败。
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'worker_id') = 0,
'ALTER TABLE user_outbox ADD COLUMN worker_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''MQ 投递 worker ID'' AFTER status',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 投递锁过期时间用于多实例 claim user_outbox老库缺这个列时 CP 通知 worker 会直接 SQL 1054。
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'lock_until_ms') = 0,
'ALTER TABLE user_outbox ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''MQ 投递锁过期时间UTC epoch ms'' AFTER worker_id',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 重试次数和下一次重试时间跟 outbox 补偿投递扫描绑定,缺列会导致 pending 查询或失败回写不可用。
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'retry_count') = 0,
'ALTER TABLE user_outbox ADD COLUMN retry_count INT NOT NULL DEFAULT 0 COMMENT ''重试次数'' AFTER lock_until_ms',
'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_outbox' AND COLUMN_NAME = 'next_retry_at_ms') = 0,
'ALTER TABLE user_outbox ADD COLUMN next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''下一次 MQ 投递重试时间UTC epoch ms'' AFTER retry_count',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 失败原因用于排查投递链路,缺列时 MarkFailed 会再次失败并掩盖真实业务错误。
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'last_error') = 0,
'ALTER TABLE user_outbox ADD COLUMN last_error VARCHAR(512) NOT NULL DEFAULT '''' COMMENT ''最后一次失败原因'' AFTER next_retry_at_ms',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- pending 索引列顺序跟 user_outbox 扫描条件绑定;历史索引存在时先删除再按当前结构重建。
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_status_created') > 0,
'ALTER TABLE user_outbox DROP INDEX idx_user_outbox_status_created',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := 'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_status_created (app_code, status, next_retry_at_ms, created_at_ms, event_id)';
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 = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_lock') = 0,
'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_lock (app_code, status, 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 = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_retention') = 0,
'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_retention (app_code, status, updated_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 = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_aggregate') = 0,
'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_aggregate (app_code, aggregate_type, aggregate_id, created_at_ms)',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS user_mic_sessions (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
mic_session_id VARCHAR(128) NOT NULL COMMENT '麦位会话 ID',

View File

@ -15,6 +15,10 @@ const (
RelationshipStatusActive = "active"
RelationshipStatusEnded = "ended"
BreakupStatusPending = "pending"
BreakupStatusConfirmed = "confirmed"
BreakupStatusCanceled = "canceled"
GiftTypeCodeCP = "cp"
EventTypeApplicationCreated = "UserCPApplicationCreated"
@ -23,6 +27,7 @@ const (
EventTypeRelationshipCreated = "UserCPRelationshipCreated"
EventTypeRelationshipIntimacyChanged = "UserCPRelationshipIntimacyChanged"
EventTypeRelationshipLevelUp = "UserCPRelationshipLevelUp"
EventTypeRelationshipEnded = "UserCPRelationshipEnded"
)
// UserProfile 是 CP 申请和关系展示用的用户快照。
@ -64,18 +69,42 @@ type Application struct {
// Relationship 表达 A/B 已建立的一种 active 关系;展示时由 service 填充 me/partner。
type Relationship struct {
RelationshipID string
RelationType string
Status string
UserAID int64
UserBID int64
Me UserProfile
Partner UserProfile
IntimacyValue int64
Level int32
CurrentLevelThreshold int64
NextLevelThreshold int64
NeededForNextLevel int64
LevelProgressPercent int32
MaxLevel bool
SourceApplicationID string
FormedAtMS int64
EndedAtMS int64
BreakupCostCoins int64
UpdatedAtMS int64
}
// RelationshipBreakup 是一次解除关系命令的占位事实pending 阻止同一关系被并发重复扣费。
type RelationshipBreakup struct {
CommandID string
RelationshipID string
RequesterUserID int64
RelationType string
BreakupCostCoins int64
Status string
UserAID int64
UserBID int64
Me UserProfile
Partner UserProfile
IntimacyValue int64
Level int32
SourceApplicationID string
FormedAtMS int64
WalletTransactionID string
CoinBalanceAfter int64
Reason string
CreatedAtMS int64
UpdatedAtMS int64
ConfirmedAtMS int64
CanceledAtMS int64
}
// GiftEvent 是 room outbox 中 RoomGiftSent 转换后的 CP 关系消费输入。

Some files were not shown because too many files have changed in this diff Show More