Fix databi statistics dimensions and backfill

This commit is contained in:
zhx 2026-06-08 12:01:55 +08:00
parent 1b6c77c6dc
commit b49a2109c7
25 changed files with 9164 additions and 5154 deletions

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,8 @@ message RoomUserJoined {
// visible_region_id / room-service // visible_region_id / room-service
int64 visible_region_id = 3; int64 visible_region_id = 3;
RoomEntryVehicleSnapshot entry_vehicle = 4; RoomEntryVehicleSnapshot entry_vehicle = 4;
int64 country_id = 5;
int64 region_id = 6;
} }
// RoomUserLeft // RoomUserLeft
@ -154,6 +156,8 @@ message RoomGiftSent {
string command_id = 8; string command_id = 8;
string pool_id = 9; string pool_id = 9;
int64 coin_spent = 10; int64 coin_spent = 10;
int64 country_id = 11;
int64 region_id = 12;
} }
// RoomHeatChanged // RoomHeatChanged

File diff suppressed because it is too large Load Diff

View File

@ -468,6 +468,8 @@ message JoinRoomRequest {
// password room-service command log // password room-service command log
string password = 3; string password = 3;
RoomEntryVehicleSnapshot entry_vehicle = 4; RoomEntryVehicleSnapshot entry_vehicle = 4;
int64 actor_country_id = 5;
int64 actor_region_id = 6;
} }
// JoinRoomResponse // JoinRoomResponse
@ -841,6 +843,8 @@ message SendGiftRequest {
int64 target_agency_owner_user_id = 10; int64 target_agency_owner_user_id = 10;
// sender_region_id gateway user-service wallet // sender_region_id gateway user-service wallet
int64 sender_region_id = 11; int64 sender_region_id = 11;
// sender_country_id gateway user-service
int64 sender_country_id = 12;
} }
// SendGiftResponse // SendGiftResponse

View File

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

@ -196,6 +196,8 @@ message AdminCreditCoinSellerStockRequest {
int64 operator_user_id = 9; int64 operator_user_id = 9;
string reason = 10; string reason = 10;
string app_code = 11; string app_code = 11;
int64 seller_country_id = 12;
int64 seller_region_id = 13;
} }
message AdminCreditCoinSellerStockResponse { message AdminCreditCoinSellerStockResponse {
@ -218,9 +220,10 @@ message TransferCoinFromSellerRequest {
int64 amount = 4; int64 amount = 4;
string reason = 5; string reason = 5;
string app_code = 6; string app_code = 6;
// seller_region_id target_region_id gateway user-service // seller_region_idtarget_region_id target_country_id gateway user-service
int64 seller_region_id = 7; int64 seller_region_id = 7;
int64 target_region_id = 8; int64 target_region_id = 8;
int64 target_country_id = 9;
} }
// TransferCoinFromSellerResponse // TransferCoinFromSellerResponse

View File

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

View File

@ -303,10 +303,23 @@ func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sell
if profile == nil || profile.Status != coinSellerStatusActive || profile.MerchantAssetType != coinSellerMerchantAssetType { if profile == nil || profile.Status != coinSellerStatusActive || profile.MerchantAssetType != coinSellerMerchantAssetType {
return nil, fmt.Errorf("target user is not an active coin seller") return nil, fmt.Errorf("target user is not an active coin seller")
} }
seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
RequestID: requestID,
Caller: "hyapp-admin-server",
UserID: sellerUserID,
})
if err != nil {
return nil, err
}
if seller == nil || seller.CountryID <= 0 || seller.RegionID <= 0 {
return nil, fmt.Errorf("coin seller country and region are required")
}
return s.walletClient.AdminCreditCoinSellerStock(ctx, &walletv1.AdminCreditCoinSellerStockRequest{ return s.walletClient.AdminCreditCoinSellerStock(ctx, &walletv1.AdminCreditCoinSellerStockRequest{
CommandId: commandID, CommandId: commandID,
SellerUserId: sellerUserID, SellerUserId: sellerUserID,
SellerCountryId: seller.CountryID,
SellerRegionId: seller.RegionID,
StockType: stockType, StockType: stockType,
CoinAmount: req.CoinAmount, CoinAmount: req.CoinAmount,
PaidCurrencyCode: paidCurrencyCode, PaidCurrencyCode: paidCurrencyCode,

View File

@ -836,11 +836,18 @@ func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) {
if !httpkit.Decode(writer, request, &body) { if !httpkit.Decode(writer, request, &body) {
return return
} }
actorCountryID, actorRegionID, err := h.resolveRoomActorCountryRegion(request)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
resp, err := h.roomClient.JoinRoom(request.Context(), &roomv1.JoinRoomRequest{ resp, err := h.roomClient.JoinRoom(request.Context(), &roomv1.JoinRoomRequest{
Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID), Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID),
Role: body.Role, Role: body.Role,
Password: body.Password, Password: body.Password,
ActorCountryId: actorCountryID,
ActorRegionId: actorRegionID,
// 座驾快照在进房命令提交前生成room-service 只保存快照,不回查 wallet。 // 座驾快照在进房命令提交前生成room-service 只保存快照,不回查 wallet。
EntryVehicle: h.joinRoomEntryVehicleSnapshot(request), EntryVehicle: h.joinRoomEntryVehicleSnapshot(request),
}) })
@ -1307,7 +1314,7 @@ func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return return
} }
senderRegionID, err := h.resolveGiftSenderRegionID(request) senderCountryID, senderRegionID, err := h.resolveRoomActorCountryRegion(request)
if err != nil { if err != nil {
httpkit.WriteRPCError(writer, request, err) httpkit.WriteRPCError(writer, request, err)
return return
@ -1326,6 +1333,7 @@ func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) {
GiftId: body.GiftID, GiftId: body.GiftID,
GiftCount: body.GiftCount, GiftCount: body.GiftCount,
PoolId: body.PoolID, PoolId: body.PoolID,
SenderCountryId: senderCountryID,
SenderRegionId: senderRegionID, SenderRegionId: senderRegionID,
TargetIsHost: targetIsHost, TargetIsHost: targetIsHost,
TargetHostRegionId: targetHostRegionID, TargetHostRegionId: targetHostRegionID,
@ -1334,21 +1342,25 @@ func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) {
httpkit.Write(writer, request, resp, err) httpkit.Write(writer, request, resp, err)
} }
func (h *Handler) resolveGiftSenderRegionID(request *http.Request) (int64, error) { func (h *Handler) resolveRoomActorCountryRegion(request *http.Request) (int64, int64, error) {
if h.userProfileClient == nil { if h.userProfileClient == nil {
return 0, xerr.New(xerr.Unavailable, "user service is not configured") return 0, 0, xerr.New(xerr.Unavailable, "user service is not configured")
} }
resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""), Meta: httpkit.UserMeta(request, ""),
UserId: auth.UserIDFromContext(request.Context()), UserId: auth.UserIDFromContext(request.Context()),
}) })
if err != nil { if err != nil {
return 0, err return 0, 0, err
} }
if resp.GetUser() == nil { if resp.GetUser() == nil {
return 0, xerr.New(xerr.NotFound, "sender not found") return 0, 0, xerr.New(xerr.NotFound, "user not found")
} }
return resp.GetUser().GetRegionId(), nil if resp.GetUser().GetCountryId() <= 0 || resp.GetUser().GetRegionId() <= 0 {
return 0, 0, xerr.New(xerr.InvalidArgument, "user country and region are required")
}
// room-service 事件必须携带用户真实国家和区域;房间 visible_region_id 只表达房间列表归属。
return resp.GetUser().GetCountryId(), resp.GetUser().GetRegionId(), nil
} }
func (h *Handler) resolveGiftTargetHostScope(request *http.Request, targetUserID int64) (bool, int64, int64, error) { func (h *Handler) resolveGiftTargetHostScope(request *http.Request, targetUserID int64) (bool, int64, int64, error) {

View File

@ -133,7 +133,7 @@ func (h *Handler) transferCoinFromSeller(writer http.ResponseWriter, request *ht
if !ok { if !ok {
return return
} }
sellerRegionID, targetRegionID, ok := h.coinSellerTransferRegions(writer, request, sellerUserID, targetUserID) sellerRegionID, targetRegionID, targetCountryID, ok := h.coinSellerTransferRegions(writer, request, sellerUserID, targetUserID)
if !ok { if !ok {
return return
} }
@ -144,6 +144,7 @@ func (h *Handler) transferCoinFromSeller(writer http.ResponseWriter, request *ht
TargetUserId: targetUserID, TargetUserId: targetUserID,
SellerRegionId: sellerRegionID, SellerRegionId: sellerRegionID,
TargetRegionId: targetRegionID, TargetRegionId: targetRegionID,
TargetCountryId: targetCountryID,
Amount: body.Amount, Amount: body.Amount,
Reason: reason, Reason: reason,
AppCode: appcode.FromContext(request.Context()), AppCode: appcode.FromContext(request.Context()),
@ -184,22 +185,24 @@ func (h *Handler) resolveCoinSellerTransferTargetUserID(writer http.ResponseWrit
return targetUserID, true return targetUserID, true
} }
func (h *Handler) coinSellerTransferRegions(writer http.ResponseWriter, request *http.Request, sellerUserID int64, targetUserID int64) (int64, int64, bool) { func (h *Handler) coinSellerTransferRegions(writer http.ResponseWriter, request *http.Request, sellerUserID int64, targetUserID int64) (int64, int64, int64, bool) {
sellerResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ sellerResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""), Meta: httpkit.UserMeta(request, ""),
UserId: sellerUserID, UserId: sellerUserID,
}) })
if err != nil { if err != nil {
httpkit.WriteRPCError(writer, request, err) httpkit.WriteRPCError(writer, request, err)
return 0, 0, false return 0, 0, 0, false
} }
sellerRegionID := sellerResp.GetUser().GetRegionId() seller := sellerResp.GetUser()
sellerCountryID := seller.GetCountryId()
sellerRegionID := seller.GetRegionId()
if sellerUserID == targetUserID { if sellerUserID == targetUserID {
if sellerRegionID <= 0 { if sellerCountryID <= 0 || sellerRegionID <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return 0, 0, false return 0, 0, 0, false
} }
return sellerRegionID, sellerRegionID, true return sellerRegionID, sellerRegionID, sellerCountryID, true
} }
targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""), Meta: httpkit.UserMeta(request, ""),
@ -207,19 +210,21 @@ func (h *Handler) coinSellerTransferRegions(writer http.ResponseWriter, request
}) })
if err != nil { if err != nil {
httpkit.WriteRPCError(writer, request, err) httpkit.WriteRPCError(writer, request, err)
return 0, 0, false return 0, 0, 0, false
} }
targetRegionID := targetResp.GetUser().GetRegionId() target := targetResp.GetUser()
if sellerRegionID <= 0 || targetRegionID <= 0 { targetCountryID := target.GetCountryId()
targetRegionID := target.GetRegionId()
if sellerRegionID <= 0 || targetCountryID <= 0 || targetRegionID <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return 0, 0, false return 0, 0, 0, false
} }
if sellerRegionID != targetRegionID { if sellerRegionID != targetRegionID {
// 充值政策当前按单一区域生效;跨区转账必须先设计明确的结算区域,不能静默套用任意一方政策。 // 充值政策当前按单一区域生效;跨区转账必须先设计明确的结算区域,不能静默套用任意一方政策。
httpkit.WriteError(writer, request, http.StatusConflict, string(xerr.Conflict), "conflict") httpkit.WriteError(writer, request, http.StatusConflict, string(xerr.Conflict), "conflict")
return 0, 0, false return 0, 0, 0, false
} }
return sellerRegionID, targetRegionID, true return sellerRegionID, targetRegionID, targetCountryID, true
} }
func walletAssetTypes(request *http.Request) []string { func walletAssetTypes(request *http.Request) []string {

View File

@ -121,6 +121,10 @@ type JoinRoom struct {
Base Base
// Role 是用户在房间内的轻量角色,默认 audience。 // Role 是用户在房间内的轻量角色,默认 audience。
Role string `json:"role"` Role string `json:"role"`
// ActorCountryID 是 gateway 从 user-service 注入的真实国家,用于统计进房活跃国家维度。
ActorCountryID int64 `json:"actor_country_id,omitempty"`
// ActorRegionID 是 gateway 从 user-service 注入的真实区域;房间 visible_region_id 不能代替用户国家。
ActorRegionID int64 `json:"actor_region_id,omitempty"`
// EntryVehicle 是 gateway 在进房时解析出的当前有效佩戴座驾快照,用于进房 IM。 // EntryVehicle 是 gateway 在进房时解析出的当前有效佩戴座驾快照,用于进房 IM。
EntryVehicle EntryVehicleSnapshot `json:"entry_vehicle,omitempty"` EntryVehicle EntryVehicleSnapshot `json:"entry_vehicle,omitempty"`
} }
@ -396,6 +400,8 @@ type SendGift struct {
GiftCount int32 `json:"gift_count"` GiftCount int32 `json:"gift_count"`
// SenderRegionID 是送礼用户所属区域,由 gateway 从 user-service 注入。 // SenderRegionID 是送礼用户所属区域,由 gateway 从 user-service 注入。
SenderRegionID int64 `json:"sender_region_id,omitempty"` SenderRegionID int64 `json:"sender_region_id,omitempty"`
// SenderCountryID 是送礼用户所属国家,由 gateway 从 user-service 注入,统计服务不能用房间区域反推。
SenderCountryID int64 `json:"sender_country_id,omitempty"`
// TargetIsHost 是 gateway 从 user-service active host profile 注入的工资入账开关。 // TargetIsHost 是 gateway 从 user-service active host profile 注入的工资入账开关。
TargetIsHost bool `json:"target_is_host,omitempty"` TargetIsHost bool `json:"target_is_host,omitempty"`
// TargetHostRegionID 是工资政策匹配区域room-service 不自行推导主播身份或区域。 // TargetHostRegionID 是工资政策匹配区域room-service 不自行推导主播身份或区域。

View File

@ -32,6 +32,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
GiftID: req.GetGiftId(), GiftID: req.GetGiftId(),
PoolID: strings.TrimSpace(req.GetPoolId()), PoolID: strings.TrimSpace(req.GetPoolId()),
GiftCount: req.GetGiftCount(), GiftCount: req.GetGiftCount(),
SenderCountryID: req.GetSenderCountryId(),
SenderRegionID: req.GetSenderRegionId(), SenderRegionID: req.GetSenderRegionId(),
TargetIsHost: req.GetTargetIsHost(), TargetIsHost: req.GetTargetIsHost(),
// 工资区域由 gateway 从 user-service host profile 注入;房间 visible_region_id 只用于房间/礼物可见性。 // 工资区域由 gateway 从 user-service host profile 注入;房间 visible_region_id 只用于房间/礼物可见性。
@ -175,6 +176,8 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
CoinSpent: targetBilling.Billing.GetCoinSpent(), CoinSpent: targetBilling.Billing.GetCoinSpent(),
BillingReceiptId: targetBilling.Billing.GetBillingReceiptId(), BillingReceiptId: targetBilling.Billing.GetBillingReceiptId(),
VisibleRegionId: roomMeta.VisibleRegionID, VisibleRegionId: roomMeta.VisibleRegionID,
CountryId: cmd.SenderCountryID,
RegionId: firstNonZeroInt64(cmd.SenderRegionID, roomMeta.VisibleRegionID),
CommandId: targetBilling.CommandID, CommandId: targetBilling.CommandID,
}) })
if err != nil { if err != nil {

View File

@ -98,6 +98,15 @@ func runtimeRoomRef(key string) loadedRoomRef {
return loadedRoomRef{AppCode: "", RoomID: key} return loadedRoomRef{AppCode: "", RoomID: key}
} }
func firstNonZeroInt64(values ...int64) int64 {
for _, value := range values {
if value != 0 {
return value
}
}
return 0
}
func commandResult(applied bool, roomVersion int64, now time.Time) *roomv1.CommandResult { func commandResult(applied bool, roomVersion int64, now time.Time) *roomv1.CommandResult {
// 所有命令响应统一带 applied、room_version 和服务端时间,便于客户端处理幂等结果。 // 所有命令响应统一带 applied、room_version 和服务端时间,便于客户端处理幂等结果。
return &roomv1.CommandResult{ return &roomv1.CommandResult{

View File

@ -27,6 +27,8 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r
cmd := command.JoinRoom{ cmd := command.JoinRoom{
Base: baseFromMeta(req.GetMeta()), Base: baseFromMeta(req.GetMeta()),
Role: req.GetRole(), Role: req.GetRole(),
ActorCountryID: req.GetActorCountryId(),
ActorRegionID: req.GetActorRegionId(),
// entry_vehicle 来自 gateway 入房瞬间的 wallet 快照,写入 command log 后可稳定重放。 // entry_vehicle 来自 gateway 入房瞬间的 wallet 快照,写入 command log 后可稳定重放。
EntryVehicle: entryVehicleSnapshotFromProto(req.GetEntryVehicle()), EntryVehicle: entryVehicleSnapshotFromProto(req.GetEntryVehicle()),
} }
@ -78,6 +80,8 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r
UserId: cmd.ActorUserID(), UserId: cmd.ActorUserID(),
Role: cmd.Role, Role: cmd.Role,
VisibleRegionId: current.VisibleRegionID, VisibleRegionId: current.VisibleRegionID,
CountryId: cmd.ActorCountryID,
RegionId: firstNonZeroInt64(cmd.ActorRegionID, current.VisibleRegionID),
// outbox 事件必须带座驾快照;否则异步补偿投递 IM 时会丢失入场座驾。 // outbox 事件必须带座驾快照;否则异步补偿投递 IM 时会丢失入场座驾。
EntryVehicle: eventEntryVehicleFromCommand(cmd.EntryVehicle), EntryVehicle: eventEntryVehicleFromCommand(cmd.EntryVehicle),
}) })

View File

@ -0,0 +1,647 @@
package main
import (
"context"
"database/sql"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"hyapp/pkg/appcode"
)
type countryRegion struct {
countryID int64
regionID int64
}
type appDayKey struct {
day string
countryID int64
regionID int64
}
type appDayAgg struct {
newUsers int64
activeUsers int64
paidUsers int64
rechargeUSDMinor int64
newUserRechargeUSDMinor int64
coinSellerRechargeUSDMinor int64
googleRechargeUSDMinor int64
coinSellerTransferCoin int64
}
type userRegistrationRow struct {
userID int64
registeredDay string
dim countryRegion
createdAtMS int64
}
type payerRow struct {
day string
userID int64
dim countryRegion
firstMS int64
}
type activeRow struct {
day string
userID int64
dim countryRegion
firstMS int64
}
type rebuildState struct {
appDays map[appDayKey]*appDayAgg
registrations []userRegistrationRow
registeredByUserDay map[string]struct{}
payersByKey map[string]payerRow
activeByKey map[string]activeRow
userIDs map[int64]struct{}
userDims map[int64]countryRegion
}
func main() {
ctx := context.Background()
var (
appFlag = flag.String("app", envDefault("APP_CODE", "lalu"), "app_code to backfill")
tzFlag = flag.String("timezone", envDefault("BACKFILL_TIMEZONE", "UTC"), "day boundary timezone, e.g. UTC or Asia/Shanghai")
startFlag = flag.String("start-day", os.Getenv("START_DAY"), "inclusive yyyy-mm-dd in timezone; defaults to last 7 days")
endFlag = flag.String("end-day", os.Getenv("END_DAY"), "inclusive yyyy-mm-dd in timezone; defaults to today")
statsDSNFlag = flag.String("statistics-dsn", firstNonEmpty(os.Getenv("STATISTICS_MYSQL_DSN"), os.Getenv("MYSQL_DSN")), "statistics mysql dsn")
userDSNFlag = flag.String("user-dsn", os.Getenv("USER_MYSQL_DSN"), "user-service mysql dsn")
walletDSNFlag = flag.String("wallet-dsn", os.Getenv("WALLET_MYSQL_DSN"), "wallet-service mysql dsn")
applyFlag = flag.Bool("apply", envBool("APPLY"), "apply replacement; default dry-run")
)
flag.Parse()
if strings.TrimSpace(*statsDSNFlag) == "" || strings.TrimSpace(*userDSNFlag) == "" || strings.TrimSpace(*walletDSNFlag) == "" {
log.Fatal("statistics-dsn, user-dsn and wallet-dsn are required")
}
loc, err := time.LoadLocation(strings.TrimSpace(*tzFlag))
if err != nil {
log.Fatalf("load timezone: %v", err)
}
startDay, endDay, windowStartMS, windowEndMS, err := resolveWindow(*startFlag, *endFlag, loc)
if err != nil {
log.Fatal(err)
}
app := appcode.Normalize(*appFlag)
statsDB := mustOpen(ctx, *statsDSNFlag)
defer statsDB.Close()
userDB := mustOpen(ctx, *userDSNFlag)
defer userDB.Close()
walletDB := mustOpen(ctx, *walletDSNFlag)
defer walletDB.Close()
state := &rebuildState{
appDays: map[appDayKey]*appDayAgg{},
registeredByUserDay: map[string]struct{}{},
payersByKey: map[string]payerRow{},
activeByKey: map[string]activeRow{},
userIDs: map[int64]struct{}{},
userDims: map[int64]countryRegion{},
}
// 第一阶段只收集窗口内会参与重算的 user_id后续统一批量加载用户国家和区域避免按充值/活跃记录逐条访问 user-service 数据库。
if err := collectRegistrationUsers(ctx, userDB, state, app, windowStartMS, windowEndMS); err != nil {
log.Fatalf("collect registrations: %v", err)
}
if err := collectActiveUserIDs(ctx, statsDB, state, app, startDay, endDay); err != nil {
log.Fatalf("collect active ids: %v", err)
}
if err := collectRechargeUserIDs(ctx, walletDB, state, app, windowStartMS, windowEndMS); err != nil {
log.Fatalf("collect recharge ids: %v", err)
}
if err := loadUserDims(ctx, userDB, state, app); err != nil {
log.Fatalf("load user dims: %v", err)
}
if err := rebuildRegistrations(ctx, userDB, state, app, windowStartMS, windowEndMS, loc); err != nil {
log.Fatalf("rebuild registrations: %v", err)
}
if err := rebuildActive(ctx, statsDB, state, app, startDay, endDay); err != nil {
log.Fatalf("rebuild active: %v", err)
}
if err := rebuildWalletRecharge(ctx, walletDB, state, app, windowStartMS, windowEndMS, loc); err != nil {
log.Fatalf("rebuild wallet recharge: %v", err)
}
if err := rebuildCoinSellerStock(ctx, walletDB, state, app, windowStartMS, windowEndMS, loc); err != nil {
log.Fatalf("rebuild coin seller stock: %v", err)
}
if err := stageAndMaybeApply(ctx, statsDB, state, app, startDay, endDay, *applyFlag); err != nil {
log.Fatalf("stage/apply: %v", err)
}
mode := "dry-run"
if *applyFlag {
mode = "applied"
}
fmt.Printf("%s app=%s timezone=%s window=%s..%s app_day_rows=%d registrations=%d active=%d payers=%d\n",
mode, app, loc.String(), startDay, endDay, len(state.appDays), len(state.registrations), len(state.activeByKey), len(state.payersByKey))
}
func envDefault(key string, fallback string) string {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
}
return fallback
}
func envBool(key string) bool {
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
case "1", "true", "yes", "y":
return true
default:
return false
}
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}
func mustOpen(ctx context.Context, dsn string) *sql.DB {
db, err := sql.Open("mysql", strings.TrimSpace(dsn))
if err != nil {
log.Fatal(err)
}
if err := db.PingContext(ctx); err != nil {
log.Fatal(err)
}
return db
}
func resolveWindow(rawStart, rawEnd string, loc *time.Location) (string, string, int64, int64, error) {
now := time.Now().In(loc)
endDayTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
startDayTime := endDayTime.AddDate(0, 0, -6)
var err error
if strings.TrimSpace(rawEnd) != "" {
endDayTime, err = parseDay(rawEnd, loc)
if err != nil {
return "", "", 0, 0, err
}
}
if strings.TrimSpace(rawStart) != "" {
startDayTime, err = parseDay(rawStart, loc)
if err != nil {
return "", "", 0, 0, err
}
}
if startDayTime.After(endDayTime) {
return "", "", 0, 0, fmt.Errorf("start-day must not be after end-day")
}
windowEnd := endDayTime.AddDate(0, 0, 1)
return formatDay(startDayTime), formatDay(endDayTime), startDayTime.UTC().UnixMilli(), windowEnd.UTC().UnixMilli(), nil
}
func parseDay(value string, loc *time.Location) (time.Time, error) {
parsed, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(value), loc)
if err != nil {
return time.Time{}, err
}
return time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, loc), nil
}
func dayFromMS(ms int64, loc *time.Location) string {
return formatDay(time.UnixMilli(ms).In(loc))
}
func formatDay(value time.Time) string {
return value.Format("2006-01-02")
}
func collectRegistrationUsers(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64) error {
rows, err := db.QueryContext(ctx, `
SELECT u.user_id
FROM users u
WHERE u.app_code = ? AND u.created_at_ms >= ? AND u.created_at_ms < ?`,
app, startMS, endMS)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var userID int64
if err := rows.Scan(&userID); err != nil {
return err
}
state.userIDs[userID] = struct{}{}
}
return rows.Err()
}
func collectActiveUserIDs(ctx context.Context, db *sql.DB, state *rebuildState, app, startDay, endDay string) error {
rows, err := db.QueryContext(ctx, `
SELECT DISTINCT user_id
FROM stat_user_day_activity
WHERE app_code = ? AND stat_day BETWEEN ? AND ?`,
app, startDay, endDay)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var userID int64
if err := rows.Scan(&userID); err != nil {
return err
}
state.userIDs[userID] = struct{}{}
}
return rows.Err()
}
func collectRechargeUserIDs(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64) error {
for _, query := range []string{
`SELECT DISTINCT user_id FROM wallet_recharge_records WHERE app_code = ? AND created_at_ms >= ? AND created_at_ms < ?`,
`SELECT DISTINCT seller_user_id FROM coin_seller_stock_records WHERE app_code = ? AND counts_as_seller_recharge = 1 AND created_at_ms >= ? AND created_at_ms < ?`,
} {
rows, err := db.QueryContext(ctx, query, app, startMS, endMS)
if err != nil {
return err
}
for rows.Next() {
var userID int64
if err := rows.Scan(&userID); err != nil {
rows.Close()
return err
}
state.userIDs[userID] = struct{}{}
}
if err := rows.Close(); err != nil {
return err
}
}
return nil
}
func loadUserDims(ctx context.Context, db *sql.DB, state *rebuildState, app string) error {
userIDs := make([]int64, 0, len(state.userIDs))
for userID := range state.userIDs {
userIDs = append(userIDs, userID)
}
sort.Slice(userIDs, func(i, j int) bool { return userIDs[i] < userIDs[j] })
for start := 0; start < len(userIDs); start += 500 {
end := start + 500
if end > len(userIDs) {
end = len(userIDs)
}
placeholders := strings.TrimRight(strings.Repeat("?,", end-start), ",")
args := []any{app}
for _, userID := range userIDs[start:end] {
args = append(args, userID)
}
rows, err := db.QueryContext(ctx, `
SELECT u.user_id, COALESCE(c.country_id, 0), COALESCE(u.region_id, 0)
FROM users u
LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country
WHERE u.app_code = ? AND u.user_id IN (`+placeholders+`)`, args...)
if err != nil {
return err
}
for rows.Next() {
var userID int64
var dim countryRegion
if err := rows.Scan(&userID, &dim.countryID, &dim.regionID); err != nil {
rows.Close()
return err
}
state.userDims[userID] = dim
}
if err := rows.Close(); err != nil {
return err
}
}
return nil
}
func rebuildRegistrations(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64, loc *time.Location) error {
// 注册国家以 users.country 映射出的真实 country_id 为准region_id 只作为独立维度写入,不能再兜底成国家。
rows, err := db.QueryContext(ctx, `
SELECT u.user_id, COALESCE(c.country_id, 0), COALESCE(u.region_id, 0), u.created_at_ms
FROM users u
LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country
WHERE u.app_code = ? AND u.created_at_ms >= ? AND u.created_at_ms < ?`,
app, startMS, endMS)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var row userRegistrationRow
if err := rows.Scan(&row.userID, &row.dim.countryID, &row.dim.regionID, &row.createdAtMS); err != nil {
return err
}
row.registeredDay = dayFromMS(row.createdAtMS, loc)
state.registrations = append(state.registrations, row)
state.registeredByUserDay[userDayKey(row.registeredDay, row.userID)] = struct{}{}
agg := state.ensureAgg(row.registeredDay, row.dim)
agg.newUsers++
}
return rows.Err()
}
func rebuildActive(ctx context.Context, db *sql.DB, state *rebuildState, app, startDay, endDay string) error {
// 活跃的原始房间事件已经被实时消费进 stat_user_day_activity离线回填只重刷该表的国家/区域快照,避免在大窗口内扫描房间流水。
rows, err := db.QueryContext(ctx, `
SELECT stat_day, user_id, MIN(first_active_at_ms)
FROM stat_user_day_activity
WHERE app_code = ? AND stat_day BETWEEN ? AND ?
GROUP BY stat_day, user_id`,
app, startDay, endDay)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var day string
var userID, firstMS int64
if err := rows.Scan(&day, &userID, &firstMS); err != nil {
return err
}
dim := state.userDims[userID]
key := fmt.Sprintf("%s:%d", day, userID)
if _, exists := state.activeByKey[key]; exists {
continue
}
state.activeByKey[key] = activeRow{day: day, userID: userID, dim: dim, firstMS: firstMS}
state.ensureAgg(day, dim).activeUsers++
}
return rows.Err()
}
func rebuildWalletRecharge(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64, loc *time.Location) error {
// wallet_recharge_records 是到账事实表Google 也会写入该表payment_orders 只是 provider 审计,不能在回填里再扫一次导致双算。
// 币商向用户转账只代表金币充值用户,不产生用户 USDT 充值Google Play 则按记录里的 usd_minor_amount 计入用户充值。
rows, err := db.QueryContext(ctx, `
SELECT rr.user_id, rr.recharge_sequence, rr.coin_amount, rr.usd_minor_amount, rr.created_at_ms, wt.biz_type
FROM wallet_recharge_records rr
JOIN wallet_transactions wt ON wt.app_code = rr.app_code AND wt.transaction_id = rr.transaction_id
WHERE rr.app_code = ? AND rr.created_at_ms >= ? AND rr.created_at_ms < ?`,
app, startMS, endMS)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var userID, sequence, coinAmount, usdMinor, createdAtMS int64
var bizType string
if err := rows.Scan(&userID, &sequence, &coinAmount, &usdMinor, &createdAtMS, &bizType); err != nil {
return err
}
day := dayFromMS(createdAtMS, loc)
dim := state.userDims[userID]
state.addPayer(day, userID, dim, createdAtMS)
agg := state.ensureAgg(day, dim)
switch strings.ToLower(strings.TrimSpace(bizType)) {
case "coin_seller_transfer":
agg.coinSellerTransferCoin += coinAmount
case "google_play_recharge":
agg.rechargeUSDMinor += usdMinor
agg.googleRechargeUSDMinor += usdMinor
default:
if usdMinor > 0 {
agg.rechargeUSDMinor += usdMinor
}
}
if sequence == 1 && usdMinor > 0 {
agg.newUserRechargeUSDMinor += usdMinor
}
}
return rows.Err()
}
func rebuildCoinSellerStock(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64, loc *time.Location) error {
// 币商进货是平台收到的 USDT 充值,按 seller 的国家/区域进入总充值,但不写入用户金币转账金额。
rows, err := db.QueryContext(ctx, `
SELECT seller_user_id, paid_amount_micro, created_at_ms
FROM coin_seller_stock_records
WHERE app_code = ? AND counts_as_seller_recharge = 1 AND paid_amount_micro > 0 AND created_at_ms >= ? AND created_at_ms < ?`,
app, startMS, endMS)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var sellerUserID, amountMicro, createdAtMS int64
if err := rows.Scan(&sellerUserID, &amountMicro, &createdAtMS); err != nil {
return err
}
usdMinor := amountMicro / 10_000
day := dayFromMS(createdAtMS, loc)
dim := state.userDims[sellerUserID]
agg := state.ensureAgg(day, dim)
agg.rechargeUSDMinor += usdMinor
agg.coinSellerRechargeUSDMinor += usdMinor
}
return rows.Err()
}
func (s *rebuildState) ensureAgg(day string, dim countryRegion) *appDayAgg {
key := appDayKey{day: day, countryID: dim.countryID, regionID: dim.regionID}
if s.appDays[key] == nil {
s.appDays[key] = &appDayAgg{}
}
return s.appDays[key]
}
func (s *rebuildState) addPayer(day string, userID int64, dim countryRegion, firstMS int64) {
key := fmt.Sprintf("%s:%d", day, userID)
if existing, exists := s.payersByKey[key]; exists && existing.firstMS <= firstMS {
return
}
if _, exists := s.payersByKey[key]; !exists {
s.ensureAgg(day, dim).paidUsers++
}
s.payersByKey[key] = payerRow{day: day, userID: userID, dim: dim, firstMS: firstMS}
}
func (s *rebuildState) userRegisteredOn(userID int64, day string) bool {
_, ok := s.registeredByUserDay[userDayKey(day, userID)]
return ok
}
func userDayKey(day string, userID int64) string {
return fmt.Sprintf("%s:%d", day, userID)
}
func stageAndMaybeApply(ctx context.Context, db *sql.DB, state *rebuildState, app, startDay, endDay string, apply bool) error {
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if err := createTempTables(ctx, tx); err != nil {
return err
}
if err := insertStageRows(ctx, tx, state, app); err != nil {
return err
}
if err := validateStage(ctx, tx); err != nil {
return err
}
if !apply {
return tx.Rollback()
}
nowMS := time.Now().UTC().UnixMilli()
// 本 job 只覆盖能从当前源表确定的近 7 天列礼物、游戏、MifaPay 等不在本次源表范围内的指标保留原值,避免修国家维度时误删其他业务数据。
if _, err := tx.ExecContext(ctx, `
UPDATE stat_app_day_country
SET new_users = 0, active_users = 0, paid_users = 0,
recharge_usd_minor = 0, new_user_recharge_usd_minor = 0,
coin_seller_recharge_usd_minor = 0,
google_recharge_usd_minor = 0, coin_seller_transfer_coin = 0,
updated_at_ms = ?
WHERE app_code = ? AND stat_day BETWEEN ? AND ?`,
nowMS, app, startDay, endDay); err != nil {
return err
}
for _, statement := range []string{
`DELETE FROM stat_user_registration WHERE app_code = ? AND registered_day BETWEEN ? AND ?`,
`DELETE FROM stat_user_day_activity WHERE app_code = ? AND stat_day BETWEEN ? AND ?`,
`DELETE FROM stat_recharge_day_payers WHERE app_code = ? AND stat_day BETWEEN ? AND ?`,
} {
if _, err := tx.ExecContext(ctx, statement, app, startDay, endDay); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO stat_app_day_country (
app_code, stat_day, country_id, region_id, new_users, active_users, paid_users,
recharge_usd_minor, new_user_recharge_usd_minor, coin_seller_recharge_usd_minor,
google_recharge_usd_minor, coin_seller_transfer_coin, updated_at_ms
)
SELECT app_code, stat_day, country_id, region_id, new_users, active_users, paid_users,
recharge_usd_minor, new_user_recharge_usd_minor, coin_seller_recharge_usd_minor,
google_recharge_usd_minor, coin_seller_transfer_coin, ?
FROM tmp_stat_backfill_app_day
ON DUPLICATE KEY UPDATE
new_users = VALUES(new_users), active_users = VALUES(active_users), paid_users = VALUES(paid_users),
recharge_usd_minor = VALUES(recharge_usd_minor),
new_user_recharge_usd_minor = VALUES(new_user_recharge_usd_minor),
coin_seller_recharge_usd_minor = VALUES(coin_seller_recharge_usd_minor),
google_recharge_usd_minor = VALUES(google_recharge_usd_minor),
coin_seller_transfer_coin = VALUES(coin_seller_transfer_coin),
updated_at_ms = VALUES(updated_at_ms)`, nowMS); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO stat_user_registration (app_code, user_id, registered_day, country_id, region_id, registered_at_ms)
SELECT app_code, user_id, registered_day, country_id, region_id, registered_at_ms FROM tmp_stat_backfill_registration`); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO stat_user_day_activity (app_code, stat_day, country_id, region_id, user_id, first_active_at_ms)
SELECT app_code, stat_day, country_id, region_id, user_id, first_active_at_ms FROM tmp_stat_backfill_active`); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO stat_recharge_day_payers (app_code, stat_day, country_id, region_id, user_id, first_paid_at_ms)
SELECT app_code, stat_day, country_id, region_id, user_id, first_paid_at_ms FROM tmp_stat_backfill_payers`); err != nil {
return err
}
return tx.Commit()
}
func createTempTables(ctx context.Context, tx *sql.Tx) error {
statements := []string{
`CREATE TEMPORARY TABLE tmp_stat_backfill_app_day (
app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL, region_id BIGINT NOT NULL,
new_users BIGINT NOT NULL, active_users BIGINT NOT NULL, paid_users BIGINT NOT NULL,
recharge_usd_minor BIGINT NOT NULL, new_user_recharge_usd_minor BIGINT NOT NULL,
coin_seller_recharge_usd_minor BIGINT NOT NULL, google_recharge_usd_minor BIGINT NOT NULL,
coin_seller_transfer_coin BIGINT NOT NULL,
PRIMARY KEY(app_code, stat_day, country_id, region_id)
)`,
`CREATE TEMPORARY TABLE tmp_stat_backfill_registration (
app_code VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, registered_day DATE NOT NULL,
country_id BIGINT NOT NULL, region_id BIGINT NOT NULL, registered_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, user_id)
)`,
`CREATE TEMPORARY TABLE tmp_stat_backfill_active (
app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL,
region_id BIGINT NOT NULL, user_id BIGINT NOT NULL, first_active_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, stat_day, user_id)
)`,
`CREATE TEMPORARY TABLE tmp_stat_backfill_payers (
app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL,
region_id BIGINT NOT NULL, user_id BIGINT NOT NULL, first_paid_at_ms BIGINT NOT NULL,
PRIMARY KEY(app_code, stat_day, user_id)
)`,
}
for _, statement := range statements {
if _, err := tx.ExecContext(ctx, statement); err != nil {
return err
}
}
return nil
}
func insertStageRows(ctx context.Context, tx *sql.Tx, state *rebuildState, app string) error {
for key, agg := range state.appDays {
if _, err := tx.ExecContext(ctx, `
INSERT INTO tmp_stat_backfill_app_day (
app_code, stat_day, country_id, region_id, new_users, active_users, paid_users,
recharge_usd_minor, new_user_recharge_usd_minor, coin_seller_recharge_usd_minor,
google_recharge_usd_minor, coin_seller_transfer_coin
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
app, key.day, key.countryID, key.regionID, agg.newUsers, agg.activeUsers, agg.paidUsers,
agg.rechargeUSDMinor, agg.newUserRechargeUSDMinor, agg.coinSellerRechargeUSDMinor,
agg.googleRechargeUSDMinor, agg.coinSellerTransferCoin); err != nil {
return err
}
}
for _, row := range state.registrations {
if _, err := tx.ExecContext(ctx, `
INSERT INTO tmp_stat_backfill_registration (app_code, user_id, registered_day, country_id, region_id, registered_at_ms)
VALUES (?, ?, ?, ?, ?, ?)`, app, row.userID, row.registeredDay, row.dim.countryID, row.dim.regionID, row.createdAtMS); err != nil {
return err
}
}
for _, row := range state.activeByKey {
if _, err := tx.ExecContext(ctx, `
INSERT INTO tmp_stat_backfill_active (app_code, stat_day, country_id, region_id, user_id, first_active_at_ms)
VALUES (?, ?, ?, ?, ?, ?)`, app, row.day, row.dim.countryID, row.dim.regionID, row.userID, row.firstMS); err != nil {
return err
}
}
for _, row := range state.payersByKey {
if _, err := tx.ExecContext(ctx, `
INSERT INTO tmp_stat_backfill_payers (app_code, stat_day, country_id, region_id, user_id, first_paid_at_ms)
VALUES (?, ?, ?, ?, ?, ?)`, app, row.day, row.dim.countryID, row.dim.regionID, row.userID, row.firstMS); err != nil {
return err
}
}
return nil
}
func validateStage(ctx context.Context, tx *sql.Tx) error {
var negativeCount int64
if err := tx.QueryRowContext(ctx, `
SELECT COUNT(*)
FROM tmp_stat_backfill_app_day
WHERE new_users < 0 OR active_users < 0 OR paid_users < 0 OR recharge_usd_minor < 0 OR coin_seller_transfer_coin < 0`).Scan(&negativeCount); err != nil {
return err
}
if negativeCount > 0 {
return fmt.Errorf("stage contains negative counters")
}
return nil
}

View File

@ -40,7 +40,7 @@ CREATE TABLE IF NOT EXISTS stat_app_day_country (
game_payout BIGINT NOT NULL DEFAULT 0 COMMENT '游戏返奖', game_payout BIGINT NOT NULL DEFAULT 0 COMMENT '游戏返奖',
game_refund BIGINT NOT NULL DEFAULT 0 COMMENT '游戏退款', game_refund BIGINT NOT NULL DEFAULT 0 COMMENT '游戏退款',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, stat_day, country_id), PRIMARY KEY (app_code, stat_day, country_id, region_id),
KEY idx_stat_app_day_region (app_code, stat_day, region_id) KEY idx_stat_app_day_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App 国家日统计聚合表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App 国家日统计聚合表';
@ -101,7 +101,7 @@ CREATE TABLE IF NOT EXISTS stat_lucky_gift_pool_day_country (
turnover_coin BIGINT NOT NULL DEFAULT 0 COMMENT '奖池幸运礼物流水金币', turnover_coin BIGINT NOT NULL DEFAULT 0 COMMENT '奖池幸运礼物流水金币',
payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '奖池幸运礼物返奖金币', payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '奖池幸运礼物返奖金币',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, stat_day, country_id, pool_id), PRIMARY KEY (app_code, stat_day, country_id, region_id, pool_id),
KEY idx_stat_lucky_pool_overview (app_code, stat_day, pool_id), KEY idx_stat_lucky_pool_overview (app_code, stat_day, pool_id),
KEY idx_stat_lucky_pool_region (app_code, stat_day, region_id) KEY idx_stat_lucky_pool_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物奖池国家日统计聚合表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物奖池国家日统计聚合表';
@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS stat_game_day_country (
refund_coin BIGINT NOT NULL DEFAULT 0, refund_coin BIGINT NOT NULL DEFAULT 0,
player_count BIGINT NOT NULL DEFAULT 0, player_count BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, stat_day, country_id, platform_code, game_id), PRIMARY KEY (app_code, stat_day, country_id, region_id, platform_code, game_id),
KEY idx_stat_game_day_region (app_code, stat_day, region_id) KEY idx_stat_game_day_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏国家日统计聚合表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏国家日统计聚合表';

View File

@ -365,8 +365,8 @@ func roomGiftEvent(body []byte) (mysqlstorage.RoomGiftEvent, bool, error) {
AppCode: envelope.GetAppCode(), AppCode: envelope.GetAppCode(),
EventID: envelope.GetEventId(), EventID: envelope.GetEventId(),
SenderUserID: gift.GetSenderUserId(), SenderUserID: gift.GetSenderUserId(),
CountryID: 0, CountryID: gift.GetCountryId(),
VisibleRegionID: gift.GetVisibleRegionId(), VisibleRegionID: firstNonZeroInt64(gift.GetRegionId(), gift.GetVisibleRegionId()),
GiftValue: gift.GetGiftValue(), GiftValue: gift.GetGiftValue(),
CoinSpent: roomGiftCoinSpent(&gift), CoinSpent: roomGiftCoinSpent(&gift),
PoolID: gift.GetPoolId(), PoolID: gift.GetPoolId(),
@ -402,8 +402,8 @@ func roomJoinActiveEvent(body []byte) (mysqlstorage.UserActiveEvent, bool, error
EventID: envelope.GetEventId(), EventID: envelope.GetEventId(),
EventType: envelope.GetEventType(), EventType: envelope.GetEventType(),
UserID: joined.GetUserId(), UserID: joined.GetUserId(),
CountryID: 0, CountryID: joined.GetCountryId(),
RegionID: joined.GetVisibleRegionId(), RegionID: firstNonZeroInt64(joined.GetRegionId(), joined.GetVisibleRegionId()),
OccurredAtMS: envelope.GetOccurredAtMs(), OccurredAtMS: envelope.GetOccurredAtMs(),
}, true, nil }, true, nil
} }

View File

@ -241,9 +241,8 @@ func (r *Repository) queryAppDayOverviewTotals(ctx context.Context, app, startDa
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
// 旧统计行曾把 region_id 写入 country_id兼容分支只服务历史读数新事件写入后会走显式 region_id。 filter += " AND region_id = ?"
filter += " AND (region_id = ? OR (region_id = 0 AND country_id = ?))" args = append(args, regionID)
args = append(args, regionID, regionID)
} }
row := r.db.QueryRowContext(ctx, ` row := r.db.QueryRowContext(ctx, `
SELECT COALESCE(SUM(new_users),0), COALESCE(SUM(active_users),0), COALESCE(SUM(paid_users),0), SELECT COALESCE(SUM(new_users),0), COALESCE(SUM(active_users),0), COALESCE(SUM(paid_users),0),
@ -272,8 +271,8 @@ func (r *Repository) queryNewPaidUsers(ctx context.Context, app, startDay, endDa
} }
if regionID > 0 { if regionID > 0 {
// p 表和 r 表都已经是统计小表,这里只按充值发生地过滤,保证新增付费用户口径和付费用户卡片一致。 // p 表和 r 表都已经是统计小表,这里只按充值发生地过滤,保证新增付费用户口径和付费用户卡片一致。
filter += " AND (p.region_id = ? OR (p.region_id = 0 AND p.country_id = ?))" filter += " AND p.region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
row := r.db.QueryRowContext(ctx, ` row := r.db.QueryRowContext(ctx, `
SELECT COUNT(DISTINCT p.user_id) SELECT COUNT(DISTINCT p.user_id)
@ -296,9 +295,8 @@ func (r *Repository) queryDailySeries(ctx context.Context, app, startDay, endDay
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
// 和总览一致兼容历史 region_id 写到 country_id 的聚合行;新数据优先读显式 region_id。 filter += " AND region_id = ?"
filter += " AND (region_id = ? OR (region_id = 0 AND country_id = ?))" args = append(args, regionID)
args = append(args, regionID, regionID)
} }
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT DATE_FORMAT(stat_day, '%Y-%m-%d'), SELECT DATE_FORMAT(stat_day, '%Y-%m-%d'),
@ -355,8 +353,8 @@ func (r *Repository) queryDailyNewPaidUsers(ctx context.Context, app, startDay,
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
filter += " AND (p.region_id = ? OR (p.region_id = 0 AND p.country_id = ?))" filter += " AND p.region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT DATE_FORMAT(p.stat_day, '%Y-%m-%d'), COUNT(DISTINCT p.user_id) SELECT DATE_FORMAT(p.stat_day, '%Y-%m-%d'), COUNT(DISTINCT p.user_id)
@ -442,8 +440,8 @@ func (r *Repository) queryRetention(ctx context.Context, app, startDay, endDay s
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
filter += " AND (r.region_id = ? OR (r.region_id = 0 AND r.country_id = ?))" filter += " AND r.region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
row := r.db.QueryRowContext(ctx, ` row := r.db.QueryRowContext(ctx, `
SELECT COUNT(*), SELECT COUNT(*),
@ -470,8 +468,8 @@ func (r *Repository) queryGameRanking(ctx context.Context, app, startDay, endDay
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
filter += " AND (region_id = ? OR (region_id = 0 AND country_id = ?))" filter += " AND region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT platform_code, game_id, COALESCE(SUM(turnover_coin),0), COALESCE(SUM(payout_coin),0), SELECT platform_code, game_id, COALESCE(SUM(turnover_coin),0), COALESCE(SUM(payout_coin),0),
@ -502,8 +500,8 @@ func (r *Repository) queryCountryBreakdown(ctx context.Context, app, startDay, e
args := []any{app, startDay, endDay} args := []any{app, startDay, endDay}
filter := "" filter := ""
if regionID > 0 { if regionID > 0 {
filter = " AND (region_id = ? OR (region_id = 0 AND country_id = ?))" filter = " AND region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT country_id, COALESCE(MAX(region_id),0), SELECT country_id, COALESCE(MAX(region_id),0),
@ -561,8 +559,8 @@ func (r *Repository) queryLuckyGiftPools(ctx context.Context, app, startDay, end
args = append(args, countryID) args = append(args, countryID)
} }
if regionID > 0 { if regionID > 0 {
filter += " AND (region_id = ? OR (region_id = 0 AND country_id = ?))" filter += " AND region_id = ?"
args = append(args, regionID, regionID) args = append(args, regionID)
} }
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT pool_id, COALESCE(SUM(turnover_coin),0), COALESCE(SUM(payout_coin),0) SELECT pool_id, COALESCE(SUM(turnover_coin),0), COALESCE(SUM(payout_coin),0)

View File

@ -113,7 +113,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
lucky_gift_turnover BIGINT NOT NULL DEFAULT 0, lucky_gift_payout BIGINT NOT NULL DEFAULT 0, lucky_gift_payers BIGINT NOT NULL DEFAULT 0, lucky_gift_turnover BIGINT NOT NULL DEFAULT 0, lucky_gift_payout BIGINT NOT NULL DEFAULT 0, lucky_gift_payers BIGINT NOT NULL DEFAULT 0,
game_turnover BIGINT NOT NULL DEFAULT 0, game_players BIGINT NOT NULL DEFAULT 0, game_turnover BIGINT NOT NULL DEFAULT 0, game_players BIGINT NOT NULL DEFAULT 0,
game_payout BIGINT NOT NULL DEFAULT 0, game_refund BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL, game_payout BIGINT NOT NULL DEFAULT 0, game_refund BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, stat_day, country_id), PRIMARY KEY (app_code, stat_day, country_id, region_id),
KEY idx_stat_app_day_region (app_code, stat_day, region_id) KEY idx_stat_app_day_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS stat_user_day_activity ( `CREATE TABLE IF NOT EXISTS stat_user_day_activity (
@ -150,7 +150,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
region_id BIGINT NOT NULL DEFAULT 0, region_id BIGINT NOT NULL DEFAULT 0,
pool_id VARCHAR(96) NOT NULL, turnover_coin BIGINT NOT NULL DEFAULT 0, payout_coin BIGINT NOT NULL DEFAULT 0, pool_id VARCHAR(96) NOT NULL, turnover_coin BIGINT NOT NULL DEFAULT 0, payout_coin BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, stat_day, country_id, pool_id), PRIMARY KEY (app_code, stat_day, country_id, region_id, pool_id),
KEY idx_stat_lucky_pool_overview (app_code, stat_day, pool_id), KEY idx_stat_lucky_pool_overview (app_code, stat_day, pool_id),
KEY idx_stat_lucky_pool_region (app_code, stat_day, region_id) KEY idx_stat_lucky_pool_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
@ -160,7 +160,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
platform_code VARCHAR(64) NOT NULL DEFAULT '', game_id VARCHAR(96) NOT NULL, platform_code VARCHAR(64) NOT NULL DEFAULT '', game_id VARCHAR(96) NOT NULL,
turnover_coin BIGINT NOT NULL DEFAULT 0, payout_coin BIGINT NOT NULL DEFAULT 0, refund_coin BIGINT NOT NULL DEFAULT 0, turnover_coin BIGINT NOT NULL DEFAULT 0, payout_coin BIGINT NOT NULL DEFAULT 0, refund_coin BIGINT NOT NULL DEFAULT 0,
player_count BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL, player_count BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, stat_day, country_id, platform_code, game_id), PRIMARY KEY (app_code, stat_day, country_id, region_id, platform_code, game_id),
KEY idx_stat_game_day_region (app_code, stat_day, region_id) KEY idx_stat_game_day_region (app_code, stat_day, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS stat_game_day_players ( `CREATE TABLE IF NOT EXISTS stat_game_day_players (
@ -205,9 +205,60 @@ func (r *Repository) Migrate(ctx context.Context) error {
return err return err
} }
} }
for table, columns := range map[string][]string{
"stat_app_day_country": {"app_code", "stat_day", "country_id", "region_id"},
"stat_lucky_gift_pool_day_country": {"app_code", "stat_day", "country_id", "region_id", "pool_id"},
"stat_game_day_country": {"app_code", "stat_day", "country_id", "region_id", "platform_code", "game_id"},
} {
if err := r.ensurePrimaryKeyColumns(ctx, table, columns); err != nil {
return err
}
}
return nil return nil
} }
func (r *Repository) ensurePrimaryKeyColumns(ctx context.Context, table string, expected []string) error {
rows, err := r.db.QueryContext(ctx, `
SELECT COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND CONSTRAINT_NAME = 'PRIMARY'
ORDER BY ORDINAL_POSITION`, table)
if err != nil {
return err
}
defer rows.Close()
actual := make([]string, 0, len(expected))
for rows.Next() {
var column string
if err := rows.Scan(&column); err != nil {
return err
}
actual = append(actual, column)
}
if err := rows.Err(); err != nil {
return err
}
if sameStringSlice(actual, expected) {
return nil
}
// 统计聚合表必须同时以国家和区域做主键;否则区域筛选和国家筛选会写入同一行,导致阿富汗这类历史错位国家继续污染榜单。
_, err = r.db.ExecContext(ctx, "ALTER TABLE "+table+" DROP PRIMARY KEY, ADD PRIMARY KEY ("+strings.Join(expected, ", ")+")")
return err
}
func sameStringSlice(left, right []string) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if left[index] != right[index] {
return false
}
}
return true
}
type UserRegisteredEvent struct { type UserRegisteredEvent struct {
AppCode string AppCode string
EventID string EventID string
@ -305,8 +356,8 @@ func (r *Repository) ConsumeUserRegistered(ctx context.Context, event UserRegist
_, err := tx.ExecContext(ctx, ` _, err := tx.ExecContext(ctx, `
UPDATE stat_app_day_country UPDATE stat_app_day_country
SET new_users = new_users + 1, updated_at_ms = ? SET new_users = new_users + 1, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -350,8 +401,8 @@ func (r *Repository) ConsumeRecharge(ctx context.Context, event RechargeEvent) e
coin_seller_transfer_coin = coin_seller_transfer_coin + ?, coin_seller_transfer_coin = coin_seller_transfer_coin + ?,
paid_users = paid_users + ?, paid_users = paid_users + ?,
updated_at_ms = ? updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, event.USDMinor, newUserUSD, coinSellerUSD, mifapayUSD, googleUSD, coinSellerTransferCoin, paidUsers, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, event.USDMinor, newUserUSD, coinSellerUSD, mifapayUSD, googleUSD, coinSellerTransferCoin, paidUsers, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -369,8 +420,8 @@ func (r *Repository) ConsumeCoinSellerStock(ctx context.Context, event CoinSelle
SET recharge_usd_minor = recharge_usd_minor + ?, SET recharge_usd_minor = recharge_usd_minor + ?,
coin_seller_recharge_usd_minor = coin_seller_recharge_usd_minor + ?, coin_seller_recharge_usd_minor = coin_seller_recharge_usd_minor + ?,
updated_at_ms = ? updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, event.USDMinor, event.USDMinor, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, event.USDMinor, event.USDMinor, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -401,8 +452,8 @@ func (r *Repository) ConsumeRoomGift(ctx context.Context, event RoomGiftEvent) e
if _, err := tx.ExecContext(ctx, ` if _, err := tx.ExecContext(ctx, `
UPDATE stat_lucky_gift_pool_day_country UPDATE stat_lucky_gift_pool_day_country
SET turnover_coin = turnover_coin + ?, updated_at_ms = ? SET turnover_coin = turnover_coin + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? AND pool_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? AND pool_id = ?
`, luckyTurnover, nowMS, appcode.Normalize(event.AppCode), day, countryID, poolID); err != nil { `, luckyTurnover, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, poolID); err != nil {
return err return err
} }
} }
@ -410,8 +461,8 @@ func (r *Repository) ConsumeRoomGift(ctx context.Context, event RoomGiftEvent) e
UPDATE stat_app_day_country UPDATE stat_app_day_country
SET gift_coin_spent = gift_coin_spent + ?, lucky_gift_turnover = lucky_gift_turnover + ?, SET gift_coin_spent = gift_coin_spent + ?, lucky_gift_turnover = lucky_gift_turnover + ?,
lucky_gift_payers = lucky_gift_payers + ?, updated_at_ms = ? lucky_gift_payers = lucky_gift_payers + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, event.CoinSpent, luckyTurnover, luckyPayers, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, event.CoinSpent, luckyTurnover, luckyPayers, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -432,16 +483,16 @@ func (r *Repository) ConsumeLuckyGiftReward(ctx context.Context, event LuckyGift
if _, err := tx.ExecContext(ctx, ` if _, err := tx.ExecContext(ctx, `
UPDATE stat_lucky_gift_pool_day_country UPDATE stat_lucky_gift_pool_day_country
SET payout_coin = payout_coin + ?, updated_at_ms = ? SET payout_coin = payout_coin + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? AND pool_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? AND pool_id = ?
`, event.Amount, nowMS, appcode.Normalize(event.AppCode), day, countryID, poolID); err != nil { `, event.Amount, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, poolID); err != nil {
return err return err
} }
} }
_, err := tx.ExecContext(ctx, ` _, err := tx.ExecContext(ctx, `
UPDATE stat_app_day_country UPDATE stat_app_day_country
SET lucky_gift_payout = lucky_gift_payout + ?, updated_at_ms = ? SET lucky_gift_payout = lucky_gift_payout + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, event.Amount, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, event.Amount, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -490,16 +541,16 @@ func (r *Repository) ConsumeGameOrder(ctx context.Context, event GameOrderEvent)
UPDATE stat_game_day_country UPDATE stat_game_day_country
SET turnover_coin = turnover_coin + ?, payout_coin = payout_coin + ?, refund_coin = refund_coin + ?, SET turnover_coin = turnover_coin + ?, payout_coin = payout_coin + ?, refund_coin = refund_coin + ?,
player_count = player_count + ?, updated_at_ms = ? player_count = player_count + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? AND platform_code = ? AND game_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? AND platform_code = ? AND game_id = ?
`, turnover, payout, refund, playerDelta, nowMS, appcode.Normalize(event.AppCode), day, countryID, event.PlatformCode, event.GameID); err != nil { `, turnover, payout, refund, playerDelta, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, event.PlatformCode, event.GameID); err != nil {
return err return err
} }
_, err := tx.ExecContext(ctx, ` _, err := tx.ExecContext(ctx, `
UPDATE stat_app_day_country UPDATE stat_app_day_country
SET game_turnover = game_turnover + ?, game_payout = game_payout + ?, game_refund = game_refund + ?, SET game_turnover = game_turnover + ?, game_payout = game_payout + ?, game_refund = game_refund + ?,
game_players = game_players + ?, updated_at_ms = ? game_players = game_players + ?, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, turnover, payout, refund, playerDelta, nowMS, appcode.Normalize(event.AppCode), day, countryID) `, turnover, payout, refund, playerDelta, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID)
return err return err
}) })
} }
@ -596,8 +647,8 @@ func applyActive(ctx context.Context, tx *sql.Tx, app string, day string, countr
_, err = tx.ExecContext(ctx, ` _, err = tx.ExecContext(ctx, `
UPDATE stat_app_day_country UPDATE stat_app_day_country
SET active_users = active_users + 1, updated_at_ms = ? SET active_users = active_users + 1, updated_at_ms = ?
WHERE app_code = ? AND stat_day = ? AND country_id = ? WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ?
`, nowMS, appcode.Normalize(app), day, countryID) `, nowMS, appcode.Normalize(app), day, countryID, regionID)
return err return err
} }
@ -606,7 +657,6 @@ func ensureAppDay(ctx context.Context, tx *sql.Tx, app string, day string, count
INSERT INTO stat_app_day_country (app_code, stat_day, country_id, region_id, updated_at_ms) INSERT INTO stat_app_day_country (app_code, stat_day, country_id, region_id, updated_at_ms)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
region_id = IF(region_id = 0 AND VALUES(region_id) > 0, VALUES(region_id), region_id),
updated_at_ms = VALUES(updated_at_ms) updated_at_ms = VALUES(updated_at_ms)
`, appcode.Normalize(app), day, countryID, regionID, nowMS) `, appcode.Normalize(app), day, countryID, regionID, nowMS)
return err return err
@ -617,7 +667,6 @@ func ensureGameDay(ctx context.Context, tx *sql.Tx, app string, day string, coun
INSERT INTO stat_game_day_country (app_code, stat_day, country_id, region_id, platform_code, game_id, updated_at_ms) INSERT INTO stat_game_day_country (app_code, stat_day, country_id, region_id, platform_code, game_id, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
region_id = IF(region_id = 0 AND VALUES(region_id) > 0, VALUES(region_id), region_id),
updated_at_ms = VALUES(updated_at_ms) updated_at_ms = VALUES(updated_at_ms)
`, appcode.Normalize(app), day, countryID, regionID, platformCode, gameID, nowMS) `, appcode.Normalize(app), day, countryID, regionID, platformCode, gameID, nowMS)
return err return err
@ -628,7 +677,6 @@ func ensureLuckyGiftPoolDay(ctx context.Context, tx *sql.Tx, app string, day str
INSERT INTO stat_lucky_gift_pool_day_country (app_code, stat_day, country_id, region_id, pool_id, updated_at_ms) INSERT INTO stat_lucky_gift_pool_day_country (app_code, stat_day, country_id, region_id, pool_id, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
region_id = IF(region_id = 0 AND VALUES(region_id) > 0, VALUES(region_id), region_id),
updated_at_ms = VALUES(updated_at_ms) updated_at_ms = VALUES(updated_at_ms)
`, appcode.Normalize(app), day, countryID, regionID, strings.TrimSpace(poolID), nowMS) `, appcode.Normalize(app), day, countryID, regionID, strings.TrimSpace(poolID), nowMS)
return err return err

View File

@ -247,6 +247,7 @@ type CoinSellerTransferCommand struct {
CommandID string CommandID string
SellerUserID int64 SellerUserID int64
TargetUserID int64 TargetUserID int64
TargetCountryID int64
SellerRegionID int64 SellerRegionID int64
TargetRegionID int64 TargetRegionID int64
Amount int64 Amount int64
@ -335,6 +336,8 @@ type CoinSellerStockCreditCommand struct {
AppCode string AppCode string
CommandID string CommandID string
SellerUserID int64 SellerUserID int64
SellerCountryID int64
SellerRegionID int64
StockType string StockType string
CoinAmount int64 CoinAmount int64
PaidCurrencyCode string PaidCurrencyCode string
@ -349,6 +352,8 @@ type CoinSellerStockCreditCommand struct {
type CoinSellerStockCreditReceipt struct { type CoinSellerStockCreditReceipt struct {
TransactionID string TransactionID string
SellerUserID int64 SellerUserID int64
SellerCountryID int64
SellerRegionID int64
StockType string StockType string
CoinAmount int64 CoinAmount int64
PaidCurrencyCode string PaidCurrencyCode string

View File

@ -315,6 +315,9 @@ func (s *Service) AdminCreditCoinSellerStock(ctx context.Context, command ledger
if command.CommandID == "" || command.SellerUserID <= 0 || command.OperatorUserID <= 0 { if command.CommandID == "" || command.SellerUserID <= 0 || command.OperatorUserID <= 0 {
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock command is incomplete") return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock command is incomplete")
} }
if command.SellerCountryID <= 0 || command.SellerRegionID <= 0 {
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "seller country and region are required")
}
if len(command.CommandID) > 128 { if len(command.CommandID) > 128 {
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long") return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
} }
@ -364,6 +367,9 @@ func (s *Service) TransferCoinFromSeller(ctx context.Context, command ledger.Coi
if command.SellerRegionID <= 0 || command.TargetRegionID <= 0 { if command.SellerRegionID <= 0 || command.TargetRegionID <= 0 {
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "seller and target region are required") return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "seller and target region are required")
} }
if command.TargetCountryID <= 0 {
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "target country is required")
}
if command.SellerRegionID != command.TargetRegionID { if command.SellerRegionID != command.TargetRegionID {
return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Conflict, "seller and target region must match") return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Conflict, "seller and target region must match")
} }

View File

@ -1452,6 +1452,7 @@ func TestWalletOutboxClaimUsesRealMySQLStatusFlow(t *testing.T) {
CommandID: "cmd-real-outbox-transfer", CommandID: "cmd-real-outbox-transfer",
SellerUserID: 30001, SellerUserID: 30001,
TargetUserID: 30002, TargetUserID: 30002,
TargetCountryID: 63,
SellerRegionID: 86, SellerRegionID: 86,
TargetRegionID: 86, TargetRegionID: 86,
Amount: 200, Amount: 200,
@ -1596,6 +1597,7 @@ func TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin(t *testing.T) {
CommandID: "cmd-seller-transfer", CommandID: "cmd-seller-transfer",
SellerUserID: 30001, SellerUserID: 30001,
TargetUserID: 30002, TargetUserID: 30002,
TargetCountryID: 63,
SellerRegionID: 1001, SellerRegionID: 1001,
TargetRegionID: 1001, TargetRegionID: 1001,
Amount: 80000, Amount: 80000,
@ -1639,6 +1641,9 @@ func TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin(t *testing.T) {
if got := repository.CountRows("wallet_outbox", "transaction_id = ?", first.TransactionID); got != 4 { if got := repository.CountRows("wallet_outbox", "transaction_id = ?", first.TransactionID); got != 4 {
t.Fatalf("seller transfer should write two balance events, one transfer event and one recharge event, got %d", got) t.Fatalf("seller transfer should write two balance events, one transfer event and one recharge event, got %d", got)
} }
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND JSON_EXTRACT(payload_json, '$.target_country_id') = 63 AND JSON_EXTRACT(payload_json, '$.country_id') = 63 AND JSON_EXTRACT(payload_json, '$.target_region_id') = 1001", first.TransactionID, "WalletRechargeRecorded"); got != 1 {
t.Fatalf("seller transfer recharge event must carry target country/region for statistics, got %d", got)
}
} }
// TestTransferCoinFromSellerAllowsSelfTransfer 验证币商可以把自己的专用库存转入自己的普通金币账户。 // TestTransferCoinFromSellerAllowsSelfTransfer 验证币商可以把自己的专用库存转入自己的普通金币账户。
@ -1662,6 +1667,7 @@ func TestTransferCoinFromSellerAllowsSelfTransfer(t *testing.T) {
CommandID: "cmd-seller-self-transfer", CommandID: "cmd-seller-self-transfer",
SellerUserID: 30003, SellerUserID: 30003,
TargetUserID: 30003, TargetUserID: 30003,
TargetCountryID: 63,
SellerRegionID: 1001, SellerRegionID: 1001,
TargetRegionID: 1001, TargetRegionID: 1001,
Amount: 30000, Amount: 30000,
@ -1721,6 +1727,7 @@ func TestTransferCoinFromSellerRecordsCoinsWithoutUSDConversion(t *testing.T) {
CommandID: "cmd-seller-small-transfer", CommandID: "cmd-seller-small-transfer",
SellerUserID: 30004, SellerUserID: 30004,
TargetUserID: 30004, TargetUserID: 30004,
TargetCountryID: 63,
SellerRegionID: 1001, SellerRegionID: 1001,
TargetRegionID: 1001, TargetRegionID: 1001,
Amount: 1, Amount: 1,
@ -1753,6 +1760,7 @@ func TestTransferCoinFromSellerInsufficientBalance(t *testing.T) {
CommandID: "cmd-seller-low", CommandID: "cmd-seller-low",
SellerUserID: 31001, SellerUserID: 31001,
TargetUserID: 31002, TargetUserID: 31002,
TargetCountryID: 63,
SellerRegionID: 1001, SellerRegionID: 1001,
TargetRegionID: 1001, TargetRegionID: 1001,
Amount: 1, Amount: 1,
@ -1787,6 +1795,7 @@ func TestTransferCoinFromSellerDoesNotRequireRechargePolicy(t *testing.T) {
CommandID: "cmd-seller-no-policy", CommandID: "cmd-seller-no-policy",
SellerUserID: 32001, SellerUserID: 32001,
TargetUserID: 32002, TargetUserID: 32002,
TargetCountryID: 63,
SellerRegionID: 2001, SellerRegionID: 2001,
TargetRegionID: 2001, TargetRegionID: 2001,
Amount: 80000, Amount: 80000,
@ -1945,6 +1954,8 @@ func TestAdminCreditCoinSellerStockPurchaseCreditsDedicatedBalance(t *testing.T)
command := ledger.CoinSellerStockCreditCommand{ command := ledger.CoinSellerStockCreditCommand{
CommandID: "cmd-stock-usdt", CommandID: "cmd-stock-usdt",
SellerUserID: 33001, SellerUserID: 33001,
SellerCountryID: 63,
SellerRegionID: 7,
StockType: ledger.StockTypeUSDTPurchase, StockType: ledger.StockTypeUSDTPurchase,
CoinAmount: 8000000, CoinAmount: 8000000,
PaidAmountMicro: 100000000, PaidAmountMicro: 100000000,
@ -1982,6 +1993,9 @@ func TestAdminCreditCoinSellerStockPurchaseCreditsDedicatedBalance(t *testing.T)
if got := repository.CountRows("wallet_outbox", "transaction_id = ?", first.TransactionID); got != 2 { if got := repository.CountRows("wallet_outbox", "transaction_id = ?", first.TransactionID); got != 2 {
t.Fatalf("stock purchase should write balance and stock outbox events, got %d", got) t.Fatalf("stock purchase should write balance and stock outbox events, got %d", got)
} }
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND JSON_EXTRACT(payload_json, '$.seller_country_id') = 63 AND JSON_EXTRACT(payload_json, '$.seller_region_id') = 7 AND JSON_EXTRACT(payload_json, '$.country_id') = 63 AND JSON_EXTRACT(payload_json, '$.region_id') = 7", first.TransactionID, "WalletCoinSellerStockPurchased"); got != 1 {
t.Fatalf("stock outbox must carry seller country/region for statistics, got %d", got)
}
} }
// TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount 锁定补偿不能携带充值金额的边界。 // TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount 锁定补偿不能携带充值金额的边界。
@ -1991,6 +2005,8 @@ func TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount(t *testing.T) {
_, err := svc.AdminCreditCoinSellerStock(context.Background(), ledger.CoinSellerStockCreditCommand{ _, err := svc.AdminCreditCoinSellerStock(context.Background(), ledger.CoinSellerStockCreditCommand{
CommandID: "cmd-stock-comp-invalid", CommandID: "cmd-stock-comp-invalid",
SellerUserID: 34001, SellerUserID: 34001,
SellerCountryID: 63,
SellerRegionID: 7,
StockType: ledger.StockTypeCoinCompensation, StockType: ledger.StockTypeCoinCompensation,
CoinAmount: 50000, CoinAmount: 50000,
PaidCurrencyCode: ledger.PaidCurrencyUSDT, PaidCurrencyCode: ledger.PaidCurrencyUSDT,
@ -2012,6 +2028,8 @@ func TestAdminCreditCoinSellerStockCompensationCreditsWithoutRechargeAmount(t *t
receipt, err := svc.AdminCreditCoinSellerStock(context.Background(), ledger.CoinSellerStockCreditCommand{ receipt, err := svc.AdminCreditCoinSellerStock(context.Background(), ledger.CoinSellerStockCreditCommand{
CommandID: "cmd-stock-comp", CommandID: "cmd-stock-comp",
SellerUserID: 35001, SellerUserID: 35001,
SellerCountryID: 63,
SellerRegionID: 7,
StockType: ledger.StockTypeCoinCompensation, StockType: ledger.StockTypeCoinCompensation,
CoinAmount: 50000, CoinAmount: 50000,
OperatorUserID: 90001, OperatorUserID: 90001,
@ -2034,6 +2052,8 @@ func TestAdminCreditCoinSellerStockRejectsDuplicatePaymentRef(t *testing.T) {
command := ledger.CoinSellerStockCreditCommand{ command := ledger.CoinSellerStockCreditCommand{
CommandID: "cmd-stock-ref-1", CommandID: "cmd-stock-ref-1",
SellerUserID: 36001, SellerUserID: 36001,
SellerCountryID: 63,
SellerRegionID: 7,
StockType: ledger.StockTypeUSDTPurchase, StockType: ledger.StockTypeUSDTPurchase,
CoinAmount: 100, CoinAmount: 100,
PaidAmountMicro: 1000000, PaidAmountMicro: 1000000,
@ -2047,6 +2067,8 @@ func TestAdminCreditCoinSellerStockRejectsDuplicatePaymentRef(t *testing.T) {
} }
command.CommandID = "cmd-stock-ref-2" command.CommandID = "cmd-stock-ref-2"
command.SellerUserID = 36002 command.SellerUserID = 36002
command.SellerCountryID = 971
command.SellerRegionID = 8
command.EvidenceRef = "oss://receipt-dup-2" command.EvidenceRef = "oss://receipt-dup-2"
_, err := svc.AdminCreditCoinSellerStock(context.Background(), command) _, err := svc.AdminCreditCoinSellerStock(context.Background(), command)
if !xerr.IsCode(err, xerr.CoinSellerPaymentRefDuplicated) { if !xerr.IsCode(err, xerr.CoinSellerPaymentRefDuplicated) {
@ -2064,6 +2086,8 @@ func TestAdminCreditCoinSellerStockRejectsIdempotencyConflict(t *testing.T) {
command := ledger.CoinSellerStockCreditCommand{ command := ledger.CoinSellerStockCreditCommand{
CommandID: "cmd-stock-conflict", CommandID: "cmd-stock-conflict",
SellerUserID: 37001, SellerUserID: 37001,
SellerCountryID: 63,
SellerRegionID: 7,
StockType: ledger.StockTypeUSDTPurchase, StockType: ledger.StockTypeUSDTPurchase,
CoinAmount: 100, CoinAmount: 100,
PaidAmountMicro: 1000000, PaidAmountMicro: 1000000,

View File

@ -1142,6 +1142,8 @@ func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command led
metadata := coinSellerStockMetadata{ metadata := coinSellerStockMetadata{
AppCode: command.AppCode, AppCode: command.AppCode,
SellerUserID: command.SellerUserID, SellerUserID: command.SellerUserID,
SellerCountryID: command.SellerCountryID,
SellerRegionID: command.SellerRegionID,
StockType: command.StockType, StockType: command.StockType,
CoinAmount: command.CoinAmount, CoinAmount: command.CoinAmount,
PaidCurrencyCode: command.PaidCurrencyCode, PaidCurrencyCode: command.PaidCurrencyCode,
@ -1241,6 +1243,7 @@ func (r *Repository) TransferCoinFromSeller(ctx context.Context, command ledger.
AppCode: command.AppCode, AppCode: command.AppCode,
SellerUserID: command.SellerUserID, SellerUserID: command.SellerUserID,
TargetUserID: command.TargetUserID, TargetUserID: command.TargetUserID,
TargetCountryID: command.TargetCountryID,
SellerRegionID: command.SellerRegionID, SellerRegionID: command.SellerRegionID,
TargetRegionID: command.TargetRegionID, TargetRegionID: command.TargetRegionID,
Amount: command.Amount, Amount: command.Amount,
@ -2676,6 +2679,7 @@ type coinSellerTransferMetadata struct {
AppCode string `json:"app_code"` AppCode string `json:"app_code"`
SellerUserID int64 `json:"seller_user_id"` SellerUserID int64 `json:"seller_user_id"`
TargetUserID int64 `json:"target_user_id"` TargetUserID int64 `json:"target_user_id"`
TargetCountryID int64 `json:"target_country_id"`
SellerRegionID int64 `json:"seller_region_id"` SellerRegionID int64 `json:"seller_region_id"`
TargetRegionID int64 `json:"target_region_id"` TargetRegionID int64 `json:"target_region_id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
@ -2728,6 +2732,8 @@ type salaryTransferToCoinSellerMetadata struct {
type coinSellerStockMetadata struct { type coinSellerStockMetadata struct {
AppCode string `json:"app_code"` AppCode string `json:"app_code"`
SellerUserID int64 `json:"seller_user_id"` SellerUserID int64 `json:"seller_user_id"`
SellerCountryID int64 `json:"seller_country_id"`
SellerRegionID int64 `json:"seller_region_id"`
StockType string `json:"stock_type"` StockType string `json:"stock_type"`
CoinAmount int64 `json:"coin_amount"` CoinAmount int64 `json:"coin_amount"`
PaidCurrencyCode string `json:"paid_currency_code"` PaidCurrencyCode string `json:"paid_currency_code"`
@ -2889,13 +2895,14 @@ func receiptFromGameMetadata(transactionID string, metadata gameCoinMetadata, id
func (r *Repository) receiptForCoinSellerStockTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.CoinSellerStockCreditReceipt, error) { func (r *Repository) receiptForCoinSellerStockTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.CoinSellerStockCreditReceipt, error) {
row := tx.QueryRowContext(ctx, row := tx.QueryRowContext(ctx,
`SELECT seller_user_id, stock_type, coin_amount, paid_currency_code, paid_amount_micro, `SELECT seller_user_id, stock_type, coin_amount, paid_currency_code, paid_amount_micro,
counts_as_seller_recharge, balance_after, created_at_ms counts_as_seller_recharge, balance_after, metadata_json, created_at_ms
FROM coin_seller_stock_records FROM coin_seller_stock_records
WHERE app_code = ? AND transaction_id = ?`, WHERE app_code = ? AND transaction_id = ?`,
appcode.FromContext(ctx), appcode.FromContext(ctx),
transactionID, transactionID,
) )
var receipt ledger.CoinSellerStockCreditReceipt var receipt ledger.CoinSellerStockCreditReceipt
var metadataJSON string
if err := row.Scan( if err := row.Scan(
&receipt.SellerUserID, &receipt.SellerUserID,
&receipt.StockType, &receipt.StockType,
@ -2904,10 +2911,18 @@ func (r *Repository) receiptForCoinSellerStockTransaction(ctx context.Context, t
&receipt.PaidAmountMicro, &receipt.PaidAmountMicro,
&receipt.CountsAsSellerRecharge, &receipt.CountsAsSellerRecharge,
&receipt.BalanceAfter, &receipt.BalanceAfter,
&metadataJSON,
&receipt.CreatedAtMS, &receipt.CreatedAtMS,
); err != nil { ); err != nil {
return ledger.CoinSellerStockCreditReceipt{}, err return ledger.CoinSellerStockCreditReceipt{}, err
} }
if metadataJSON != "" {
var metadata coinSellerStockMetadata
if err := json.Unmarshal([]byte(metadataJSON), &metadata); err == nil {
receipt.SellerCountryID = metadata.SellerCountryID
receipt.SellerRegionID = metadata.SellerRegionID
}
}
receipt.TransactionID = transactionID receipt.TransactionID = transactionID
return receipt, nil return receipt, nil
} }
@ -2916,6 +2931,8 @@ func receiptFromCoinSellerStockMetadata(transactionID string, metadata coinSelle
return ledger.CoinSellerStockCreditReceipt{ return ledger.CoinSellerStockCreditReceipt{
TransactionID: transactionID, TransactionID: transactionID,
SellerUserID: metadata.SellerUserID, SellerUserID: metadata.SellerUserID,
SellerCountryID: metadata.SellerCountryID,
SellerRegionID: metadata.SellerRegionID,
StockType: metadata.StockType, StockType: metadata.StockType,
CoinAmount: metadata.CoinAmount, CoinAmount: metadata.CoinAmount,
PaidCurrencyCode: metadata.PaidCurrencyCode, PaidCurrencyCode: metadata.PaidCurrencyCode,
@ -3070,6 +3087,34 @@ func coinSellerTransferredEvent(transactionID string, commandID string, sellerUs
} }
func rechargeRecordedEvent(transactionID string, commandID string, targetUserID int64, availableDelta int64, payload any, nowMs int64) walletOutboxEvent { func rechargeRecordedEvent(transactionID string, commandID string, targetUserID int64, availableDelta int64, payload any, nowMs int64) walletOutboxEvent {
if metadata, ok := payload.(coinSellerTransferMetadata); ok {
// WalletRechargeRecorded 是统计服务的充值入口;币商转账没有 USDT 金额,但必须带目标用户真实国家/区域,避免统计继续把 region 当 country。
payload = map[string]any{
"app_code": metadata.AppCode,
"seller_user_id": metadata.SellerUserID,
"target_user_id": metadata.TargetUserID,
"target_country_id": metadata.TargetCountryID,
"country_id": metadata.TargetCountryID,
"seller_region_id": metadata.SellerRegionID,
"target_region_id": metadata.TargetRegionID,
"region_id": metadata.TargetRegionID,
"amount": metadata.Amount,
"reason": metadata.Reason,
"seller_asset_type": metadata.SellerAssetType,
"target_asset_type": metadata.TargetAssetType,
"seller_balance_after": metadata.SellerBalanceAfter,
"target_balance_after": metadata.TargetBalanceAfter,
"recharge_sequence": metadata.RechargeSequence,
"recharge_usd_minor": metadata.RechargeUSDMinor,
"recharge_currency_code": metadata.RechargeCurrencyCode,
"recharge_policy_id": metadata.RechargePolicyID,
"recharge_policy_version": metadata.RechargePolicyVersion,
"recharge_policy_coin_amount": metadata.RechargePolicyCoinAmount,
"recharge_policy_usd_minor_amount": metadata.RechargePolicyUSDMinorAmount,
"recharge_type": "coin_seller_transfer",
"created_at_ms": nowMs,
}
}
return walletOutboxEvent{ return walletOutboxEvent{
EventID: eventID(transactionID, "WalletRechargeRecorded", targetUserID, ledger.AssetCoin), EventID: eventID(transactionID, "WalletRechargeRecorded", targetUserID, ledger.AssetCoin),
EventType: "WalletRechargeRecorded", EventType: "WalletRechargeRecorded",
@ -3184,6 +3229,10 @@ func coinSellerStockCreditedEvent(transactionID string, commandID string, metada
"transaction_id": transactionID, "transaction_id": transactionID,
"command_id": commandID, "command_id": commandID,
"seller_user_id": metadata.SellerUserID, "seller_user_id": metadata.SellerUserID,
"seller_country_id": metadata.SellerCountryID,
"seller_region_id": metadata.SellerRegionID,
"country_id": metadata.SellerCountryID,
"region_id": metadata.SellerRegionID,
"stock_type": metadata.StockType, "stock_type": metadata.StockType,
"coin_amount": metadata.CoinAmount, "coin_amount": metadata.CoinAmount,
"paid_currency_code": metadata.PaidCurrencyCode, "paid_currency_code": metadata.PaidCurrencyCode,
@ -3451,9 +3500,11 @@ func gameBizTypeAndDelta(opType string, coinAmount int64) (string, int64, error)
} }
func coinSellerStockRequestHash(command ledger.CoinSellerStockCreditCommand) string { func coinSellerStockRequestHash(command ledger.CoinSellerStockCreditCommand) string {
return stableHash(fmt.Sprintf("coin_seller_stock|%s|%d|%s|%d|%s|%d|%s|%s|%d|%s", return stableHash(fmt.Sprintf("coin_seller_stock|%s|%d|%d|%d|%s|%d|%s|%d|%s|%s|%d|%s",
appcode.Normalize(command.AppCode), appcode.Normalize(command.AppCode),
command.SellerUserID, command.SellerUserID,
command.SellerCountryID,
command.SellerRegionID,
command.StockType, command.StockType,
command.CoinAmount, command.CoinAmount,
command.PaidCurrencyCode, command.PaidCurrencyCode,

View File

@ -842,6 +842,8 @@ func (s *Server) AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.A
AppCode: req.GetAppCode(), AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(), CommandID: req.GetCommandId(),
SellerUserID: req.GetSellerUserId(), SellerUserID: req.GetSellerUserId(),
SellerCountryID: req.GetSellerCountryId(),
SellerRegionID: req.GetSellerRegionId(),
StockType: req.GetStockType(), StockType: req.GetStockType(),
CoinAmount: req.GetCoinAmount(), CoinAmount: req.GetCoinAmount(),
PaidCurrencyCode: req.GetPaidCurrencyCode(), PaidCurrencyCode: req.GetPaidCurrencyCode(),
@ -876,6 +878,7 @@ func (s *Server) TransferCoinFromSeller(ctx context.Context, req *walletv1.Trans
CommandID: req.GetCommandId(), CommandID: req.GetCommandId(),
SellerUserID: req.GetSellerUserId(), SellerUserID: req.GetSellerUserId(),
TargetUserID: req.GetTargetUserId(), TargetUserID: req.GetTargetUserId(),
TargetCountryID: req.GetTargetCountryId(),
SellerRegionID: req.GetSellerRegionId(), SellerRegionID: req.GetSellerRegionId(),
TargetRegionID: req.GetTargetRegionId(), TargetRegionID: req.GetTargetRegionId(),
Amount: req.GetAmount(), Amount: req.GetAmount(),