fix: sync host private messages from IM history

This commit is contained in:
zhx 2026-07-23 15:11:10 +08:00
parent 42e4adab56
commit 180d7ecd8b
17 changed files with 900 additions and 125 deletions

View File

@ -15143,7 +15143,7 @@ const file_proto_user_v1_user_proto_rawDesc = "" +
"\x17ListCPFormationGiftFeed\x12-.hyapp.user.v1.ListCPFormationGiftFeedRequest\x1a..hyapp.user.v1.ListCPFormationGiftFeedResponse2\x88\x02\n" + "\x17ListCPFormationGiftFeed\x12-.hyapp.user.v1.ListCPFormationGiftFeedRequest\x1a..hyapp.user.v1.ListCPFormationGiftFeedResponse2\x88\x02\n" +
"\x15UserCPInternalService\x12u\n" + "\x15UserCPInternalService\x12u\n" +
"\x16ConsumeRoomGiftCPEvent\x12,.hyapp.user.v1.ConsumeRoomGiftCPEventRequest\x1a-.hyapp.user.v1.ConsumeRoomGiftCPEventResponse\x12x\n" + "\x16ConsumeRoomGiftCPEvent\x12,.hyapp.user.v1.ConsumeRoomGiftCPEventRequest\x1a-.hyapp.user.v1.ConsumeRoomGiftCPEventResponse\x12x\n" +
"\x17ListCPWeeklyRankEntries\x12-.hyapp.user.v1.ListCPWeeklyRankEntriesRequest\x1a..hyapp.user.v1.ListCPWeeklyRankEntriesResponse2\xab\x05\n" + "\x17ListCPWeeklyRankEntries\x12-.hyapp.user.v1.ListCPWeeklyRankEntriesRequest\x1a..hyapp.user.v1.ListCPWeeklyRankEntriesResponse2\x93\x06\n" +
"\x0fUserCronService\x12\\\n" + "\x0fUserCronService\x12\\\n" +
"\x17ProcessLoginIPRiskBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" + "\x17ProcessLoginIPRiskBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" +
"\x19ProcessRegionRebuildBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" + "\x19ProcessRegionRebuildBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" +
@ -15151,7 +15151,8 @@ const file_proto_user_v1_user_proto_rawDesc = "" +
"\x1aCompensateRoomOpenSessions\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12\\\n" + "\x1aCompensateRoomOpenSessions\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12\\\n" +
"\x17ExpireManagerUserBlocks\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12X\n" + "\x17ExpireManagerUserBlocks\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12X\n" +
"\x13ExpireAdminUserBans\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12a\n" + "\x13ExpireAdminUserBans\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12a\n" +
"\x1cRefreshCPIntimacyLeaderboard\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse2\xd1\x01\n" + "\x1cRefreshCPIntimacyLeaderboard\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12f\n" +
"!ProcessPrivateMessageHistoryBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse2\xd1\x01\n" +
"\x11UserDeviceService\x12Z\n" + "\x11UserDeviceService\x12Z\n" +
"\rBindPushToken\x12#.hyapp.user.v1.BindPushTokenRequest\x1a$.hyapp.user.v1.BindPushTokenResponse\x12`\n" + "\rBindPushToken\x12#.hyapp.user.v1.BindPushTokenRequest\x1a$.hyapp.user.v1.BindPushTokenResponse\x12`\n" +
"\x0fDeletePushToken\x12%.hyapp.user.v1.DeletePushTokenRequest\x1a&.hyapp.user.v1.DeletePushTokenResponse2\xb4\x01\n" + "\x0fDeletePushToken\x12%.hyapp.user.v1.DeletePushTokenRequest\x1a&.hyapp.user.v1.DeletePushTokenResponse2\xb4\x01\n" +
@ -15634,120 +15635,122 @@ var file_proto_user_v1_user_proto_depIdxs = []int32{
14, // 232: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest 14, // 232: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest
14, // 233: hyapp.user.v1.UserCronService.ExpireAdminUserBans:input_type -> hyapp.user.v1.CronBatchRequest 14, // 233: hyapp.user.v1.UserCronService.ExpireAdminUserBans:input_type -> hyapp.user.v1.CronBatchRequest
14, // 234: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest 14, // 234: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest
130, // 235: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest 14, // 235: hyapp.user.v1.UserCronService.ProcessPrivateMessageHistoryBatch:input_type -> hyapp.user.v1.CronBatchRequest
132, // 236: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest 130, // 236: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest
3, // 237: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest 132, // 237: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest
186, // 238: hyapp.user.v1.AppRegistryService.ListApps:input_type -> hyapp.user.v1.ListAppsRequest 3, // 238: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest
136, // 239: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest 186, // 239: hyapp.user.v1.AppRegistryService.ListApps:input_type -> hyapp.user.v1.ListAppsRequest
138, // 240: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest 136, // 240: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest
140, // 241: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest 138, // 241: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest
143, // 242: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest 140, // 242: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest
145, // 243: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest 143, // 243: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest
147, // 244: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest 145, // 244: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest
148, // 245: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest 147, // 245: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest
149, // 246: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest 148, // 246: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest
152, // 247: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest 149, // 247: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest
154, // 248: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest 152, // 248: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest
188, // 249: hyapp.user.v1.UserIdentityService.ResolveAdminUserIdentifier:input_type -> hyapp.user.v1.ResolveAdminUserIdentifierRequest 154, // 249: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest
156, // 250: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest 188, // 250: hyapp.user.v1.UserIdentityService.ResolveAdminUserIdentifier:input_type -> hyapp.user.v1.ResolveAdminUserIdentifierRequest
158, // 251: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest 156, // 251: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest
165, // 252: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest 158, // 252: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest
167, // 253: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest 165, // 253: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest
160, // 254: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest 167, // 254: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest
169, // 255: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest 160, // 255: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest
171, // 256: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest 169, // 256: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest
172, // 257: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest 171, // 257: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest
174, // 258: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest 172, // 258: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest
176, // 259: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest 174, // 259: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest
178, // 260: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest 176, // 260: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest
179, // 261: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:input_type -> hyapp.user.v1.RecyclePrettyDisplayIDRequest 178, // 261: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest
181, // 262: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest 179, // 262: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:input_type -> hyapp.user.v1.RecyclePrettyDisplayIDRequest
18, // 263: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse 181, // 263: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest
10, // 264: hyapp.user.v1.UserService.GetInviteAttribution:output_type -> hyapp.user.v1.GetInviteAttributionResponse 18, // 264: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse
21, // 265: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse 10, // 265: hyapp.user.v1.UserService.GetInviteAttribution:output_type -> hyapp.user.v1.GetInviteAttributionResponse
24, // 266: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse 21, // 266: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse
82, // 267: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse 24, // 267: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse
86, // 268: hyapp.user.v1.UserService.BatchGetUserAdminProfiles:output_type -> hyapp.user.v1.BatchGetUserAdminProfilesResponse 82, // 268: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse
88, // 269: hyapp.user.v1.UserService.AdminIssueUserAccessToken:output_type -> hyapp.user.v1.AdminIssueUserAccessTokenResponse 86, // 269: hyapp.user.v1.UserService.BatchGetUserAdminProfiles:output_type -> hyapp.user.v1.BatchGetUserAdminProfilesResponse
91, // 270: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:output_type -> hyapp.user.v1.BatchGetRoomBasicUsersResponse 88, // 270: hyapp.user.v1.UserService.AdminIssueUserAccessToken:output_type -> hyapp.user.v1.AdminIssueUserAccessTokenResponse
93, // 271: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse 91, // 271: hyapp.user.v1.UserService.BatchGetRoomBasicUsers:output_type -> hyapp.user.v1.BatchGetRoomBasicUsersResponse
13, // 272: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse 93, // 272: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse
98, // 273: hyapp.user.v1.UserService.AuthorizeUserAvatarUpload:output_type -> hyapp.user.v1.AuthorizeUserAvatarUploadResponse 13, // 273: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse
100, // 274: hyapp.user.v1.UserService.CompleteUserAvatarUpload:output_type -> hyapp.user.v1.CompleteUserAvatarUploadResponse 98, // 274: hyapp.user.v1.UserService.AuthorizeUserAvatarUpload:output_type -> hyapp.user.v1.AuthorizeUserAvatarUploadResponse
95, // 275: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse 100, // 275: hyapp.user.v1.UserService.CompleteUserAvatarUpload:output_type -> hyapp.user.v1.CompleteUserAvatarUploadResponse
102, // 276: hyapp.user.v1.UserService.UpdateUserProfileBackground:output_type -> hyapp.user.v1.UpdateUserProfileBackgroundResponse 95, // 276: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse
104, // 277: hyapp.user.v1.UserService.UpdateUserContactInfo:output_type -> hyapp.user.v1.UpdateUserContactInfoResponse 102, // 277: hyapp.user.v1.UserService.UpdateUserProfileBackground:output_type -> hyapp.user.v1.UpdateUserProfileBackgroundResponse
106, // 278: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:output_type -> hyapp.user.v1.UpdateUserWithdrawAddressResponse 104, // 278: hyapp.user.v1.UserService.UpdateUserContactInfo:output_type -> hyapp.user.v1.UpdateUserContactInfoResponse
109, // 279: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse 106, // 279: hyapp.user.v1.UserService.UpdateUserWithdrawAddress:output_type -> hyapp.user.v1.UpdateUserWithdrawAddressResponse
109, // 280: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse 109, // 280: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse
111, // 281: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse 109, // 281: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse
114, // 282: hyapp.user.v1.UserService.AdminBanUser:output_type -> hyapp.user.v1.AdminBanUserResponse 111, // 282: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse
116, // 283: hyapp.user.v1.UserService.AdminUnbanUser:output_type -> hyapp.user.v1.AdminUnbanUserResponse 114, // 283: hyapp.user.v1.UserService.AdminBanUser:output_type -> hyapp.user.v1.AdminBanUserResponse
119, // 284: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse 116, // 284: hyapp.user.v1.UserService.AdminUnbanUser:output_type -> hyapp.user.v1.AdminUnbanUserResponse
121, // 285: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse 119, // 285: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse
123, // 286: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse 121, // 286: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse
125, // 287: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse 123, // 287: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse
127, // 288: hyapp.user.v1.UserService.SearchInviteReferrer:output_type -> hyapp.user.v1.SearchInviteReferrerResponse 125, // 288: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse
129, // 289: hyapp.user.v1.UserService.BindInviteReferrer:output_type -> hyapp.user.v1.BindInviteReferrerResponse 127, // 289: hyapp.user.v1.UserService.SearchInviteReferrer:output_type -> hyapp.user.v1.SearchInviteReferrerResponse
26, // 290: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse 129, // 290: hyapp.user.v1.UserService.BindInviteReferrer:output_type -> hyapp.user.v1.BindInviteReferrerResponse
29, // 291: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse 26, // 291: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse
31, // 292: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse 29, // 292: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse
33, // 293: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse 31, // 293: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse
36, // 294: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse 33, // 294: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse
38, // 295: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse 36, // 295: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse
40, // 296: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse 38, // 296: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse
42, // 297: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse 40, // 297: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse
45, // 298: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse 42, // 298: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse
48, // 299: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse 45, // 299: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse
80, // 300: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse 48, // 300: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse
60, // 301: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse 80, // 301: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse
62, // 302: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse 60, // 302: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse
64, // 303: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse 62, // 303: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse
66, // 304: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse 64, // 304: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse
68, // 305: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse 66, // 305: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse
70, // 306: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse 68, // 306: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse
72, // 307: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse 70, // 307: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse
74, // 308: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse 72, // 308: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse
185, // 309: hyapp.user.v1.UserCPService.ListCPFormationGiftFeed:output_type -> hyapp.user.v1.ListCPFormationGiftFeedResponse 74, // 309: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse
77, // 310: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse 185, // 310: hyapp.user.v1.UserCPService.ListCPFormationGiftFeed:output_type -> hyapp.user.v1.ListCPFormationGiftFeedResponse
58, // 311: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:output_type -> hyapp.user.v1.ListCPWeeklyRankEntriesResponse 77, // 311: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse
15, // 312: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse 58, // 312: hyapp.user.v1.UserCPInternalService.ListCPWeeklyRankEntries:output_type -> hyapp.user.v1.ListCPWeeklyRankEntriesResponse
15, // 313: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse 15, // 313: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse
15, // 314: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse 15, // 314: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse
15, // 315: hyapp.user.v1.UserCronService.CompensateRoomOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse 15, // 315: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse
15, // 316: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse 15, // 316: hyapp.user.v1.UserCronService.CompensateRoomOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse
15, // 317: hyapp.user.v1.UserCronService.ExpireAdminUserBans:output_type -> hyapp.user.v1.CronBatchResponse 15, // 317: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse
15, // 318: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse 15, // 318: hyapp.user.v1.UserCronService.ExpireAdminUserBans:output_type -> hyapp.user.v1.CronBatchResponse
131, // 319: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse 15, // 319: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse
133, // 320: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse 15, // 320: hyapp.user.v1.UserCronService.ProcessPrivateMessageHistoryBatch:output_type -> hyapp.user.v1.CronBatchResponse
4, // 321: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse 131, // 321: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse
187, // 322: hyapp.user.v1.AppRegistryService.ListApps:output_type -> hyapp.user.v1.ListAppsResponse 133, // 322: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse
137, // 323: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse 4, // 323: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse
139, // 324: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse 187, // 324: hyapp.user.v1.AppRegistryService.ListApps:output_type -> hyapp.user.v1.ListAppsResponse
141, // 325: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse 137, // 325: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse
144, // 326: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse 139, // 326: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse
146, // 327: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse 141, // 327: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse
150, // 328: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse 144, // 328: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse
150, // 329: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse 146, // 329: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse
150, // 330: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse 150, // 330: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse
153, // 331: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse 150, // 331: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse
155, // 332: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse 150, // 332: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse
189, // 333: hyapp.user.v1.UserIdentityService.ResolveAdminUserIdentifier:output_type -> hyapp.user.v1.ResolveAdminUserIdentifierResponse 153, // 333: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse
157, // 334: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse 155, // 334: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse
159, // 335: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse 189, // 335: hyapp.user.v1.UserIdentityService.ResolveAdminUserIdentifier:output_type -> hyapp.user.v1.ResolveAdminUserIdentifierResponse
166, // 336: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse 157, // 336: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse
168, // 337: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse 159, // 337: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse
161, // 338: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse 166, // 338: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse
170, // 339: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse 168, // 339: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse
173, // 340: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse 161, // 340: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse
173, // 341: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse 170, // 341: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse
175, // 342: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse 173, // 342: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse
177, // 343: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse 173, // 343: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse
180, // 344: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse 175, // 344: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse
180, // 345: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:output_type -> hyapp.user.v1.PrettyDisplayIDResponse 177, // 345: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse
182, // 346: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse 180, // 346: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse
263, // [263:347] is the sub-list for method output_type 180, // 347: hyapp.user.v1.UserPrettyDisplayIDAdminService.RecyclePrettyDisplayID:output_type -> hyapp.user.v1.PrettyDisplayIDResponse
179, // [179:263] is the sub-list for method input_type 182, // 348: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse
264, // [264:349] is the sub-list for method output_type
179, // [179:264] is the sub-list for method input_type
179, // [179:179] is the sub-list for extension type_name 179, // [179:179] is the sub-list for extension type_name
179, // [179:179] is the sub-list for extension extendee 179, // [179:179] is the sub-list for extension extendee
0, // [0:179] is the sub-list for field type_name 0, // [0:179] is the sub-list for field type_name

View File

@ -1613,6 +1613,7 @@ service UserCronService {
rpc ExpireManagerUserBlocks(CronBatchRequest) returns (CronBatchResponse); rpc ExpireManagerUserBlocks(CronBatchRequest) returns (CronBatchResponse);
rpc ExpireAdminUserBans(CronBatchRequest) returns (CronBatchResponse); rpc ExpireAdminUserBans(CronBatchRequest) returns (CronBatchResponse);
rpc RefreshCPIntimacyLeaderboard(CronBatchRequest) returns (CronBatchResponse); rpc RefreshCPIntimacyLeaderboard(CronBatchRequest) returns (CronBatchResponse);
rpc ProcessPrivateMessageHistoryBatch(CronBatchRequest) returns (CronBatchResponse);
} }
// UserDeviceService App token // UserDeviceService App token

View File

@ -2160,6 +2160,7 @@ const (
UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks" UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks"
UserCronService_ExpireAdminUserBans_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireAdminUserBans" UserCronService_ExpireAdminUserBans_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireAdminUserBans"
UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName = "/hyapp.user.v1.UserCronService/RefreshCPIntimacyLeaderboard" UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName = "/hyapp.user.v1.UserCronService/RefreshCPIntimacyLeaderboard"
UserCronService_ProcessPrivateMessageHistoryBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessPrivateMessageHistoryBatch"
) )
// UserCronServiceClient is the client API for UserCronService service. // UserCronServiceClient is the client API for UserCronService service.
@ -2175,6 +2176,7 @@ type UserCronServiceClient interface {
ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
ExpireAdminUserBans(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ExpireAdminUserBans(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
ProcessPrivateMessageHistoryBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error)
} }
type userCronServiceClient struct { type userCronServiceClient struct {
@ -2255,6 +2257,16 @@ func (c *userCronServiceClient) RefreshCPIntimacyLeaderboard(ctx context.Context
return out, nil return out, nil
} }
func (c *userCronServiceClient) ProcessPrivateMessageHistoryBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CronBatchResponse)
err := c.cc.Invoke(ctx, UserCronService_ProcessPrivateMessageHistoryBatch_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserCronServiceServer is the server API for UserCronService service. // UserCronServiceServer is the server API for UserCronService service.
// All implementations must embed UnimplementedUserCronServiceServer // All implementations must embed UnimplementedUserCronServiceServer
// for forward compatibility. // for forward compatibility.
@ -2268,6 +2280,7 @@ type UserCronServiceServer interface {
ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
ProcessPrivateMessageHistoryBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error)
mustEmbedUnimplementedUserCronServiceServer() mustEmbedUnimplementedUserCronServiceServer()
} }
@ -2299,6 +2312,9 @@ func (UnimplementedUserCronServiceServer) ExpireAdminUserBans(context.Context, *
func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented") return nil, status.Error(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented")
} }
func (UnimplementedUserCronServiceServer) ProcessPrivateMessageHistoryBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ProcessPrivateMessageHistoryBatch not implemented")
}
func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {} func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {}
func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {} func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {}
@ -2446,6 +2462,24 @@ func _UserCronService_RefreshCPIntimacyLeaderboard_Handler(srv interface{}, ctx
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _UserCronService_ProcessPrivateMessageHistoryBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CronBatchRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserCronServiceServer).ProcessPrivateMessageHistoryBatch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserCronService_ProcessPrivateMessageHistoryBatch_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserCronServiceServer).ProcessPrivateMessageHistoryBatch(ctx, req.(*CronBatchRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserCronService_ServiceDesc is the grpc.ServiceDesc for UserCronService service. // UserCronService_ServiceDesc is the grpc.ServiceDesc for UserCronService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -2481,6 +2515,10 @@ var UserCronService_ServiceDesc = grpc.ServiceDesc{
MethodName: "RefreshCPIntimacyLeaderboard", MethodName: "RefreshCPIntimacyLeaderboard",
Handler: _UserCronService_RefreshCPIntimacyLeaderboard_Handler, Handler: _UserCronService_RefreshCPIntimacyLeaderboard_Handler,
}, },
{
MethodName: "ProcessPrivateMessageHistoryBatch",
Handler: _UserCronService_ProcessPrivateMessageHistoryBatch_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "proto/user/v1/user.proto", Metadata: "proto/user/v1/user.proto",

176
pkg/tencentim/history.go Normal file
View File

@ -0,0 +1,176 @@
package tencentim
import (
"compress/gzip"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
const (
// historyFileCommand 获取一个自然小时的 C2C 消息归档下载地址;它是离线统计接口,
// 不进入客户端发消息链路,也不会把业务服务延迟传播到腾讯 IM。
historyFileCommand = "v4/open_msg_svc/get_history"
// 小时归档通常是 MB 级文件,不能复用实时 REST 调用的 5 秒传输预算;
// cron RPC 仍有 2 分钟总 deadline这里只放宽单文件下载而不影响发消息接口。
historyDownloadTimeout = 60 * time.Second
)
var tencentHistoryLocation = time.FixedZone("UTC+8", 8*60*60)
// C2CHistoryFile 是腾讯 IM 返回的短期下载凭据。URL 含临时授权信息,只能在内存中使用,
// 调用方不得把它写入日志、数据库或业务错误。
type C2CHistoryFile struct {
URL string `json:"URL"`
FileSize int64 `json:"FileSize"`
GzipSize int64 `json:"GzipSize"`
}
// C2CHistoryMessage 只解析私聊人数统计所需字段。MsgBody 等正文由流式 decoder 跳过,
// 避免消息内容进入 user-service 内存对象、日志或统计库。
type C2CHistoryMessage struct {
FromAccount string `json:"From_Account"`
ToAccount string `json:"To_Account"`
Timestamp int64 `json:"MsgTimestamp"`
Sequence uint64 `json:"MsgSeq"`
Random uint64 `json:"MsgRandom"`
}
type historyFilesRequest struct {
ChatType string `json:"ChatType"`
MsgTime string `json:"MsgTime"`
}
type historyFilesResponse struct {
restResponse
Files []C2CHistoryFile `json:"File"`
}
// ListC2CHistoryFiles 获取指定 UTC 小时对应的 C2C 归档。
// 腾讯接口的 MsgTime 使用 UTC+8 小时字符串,而消息本身仍携带 Unix 时间戳;在 REST
// 边界集中转换可防止 cron 把 UTC 小时错取成前一天的数据。
func (c *RESTClient) ListC2CHistoryFiles(ctx context.Context, hourStartUTC time.Time) ([]C2CHistoryFile, error) {
hourStartUTC = hourStartUTC.UTC().Truncate(time.Hour)
request := historyFilesRequest{
ChatType: "C2C",
MsgTime: hourStartUTC.In(tencentHistoryLocation).Format("2006010215"),
}
var response historyFilesResponse
if err := c.post(ctx, historyFileCommand, request, &response); err != nil {
return nil, err
}
if err := response.err(); err != nil {
return nil, err
}
files := make([]C2CHistoryFile, 0, len(response.Files))
for _, file := range response.Files {
file.URL = strings.TrimSpace(file.URL)
if file.URL == "" {
continue
}
files = append(files, file)
}
return files, nil
}
// StreamC2CHistory 下载并逐条解码一个 gzip 归档。decoder 只保留当前消息的统计字段,
// 即使单小时归档很大也不会把整个 MsgList 或消息正文一次性加载进内存。
func (c *RESTClient) StreamC2CHistory(ctx context.Context, file C2CHistoryFile, consume func(C2CHistoryMessage) error) (int, error) {
if c == nil || c.httpClient == nil {
return 0, fmt.Errorf("tencent im rest client is not configured")
}
if consume == nil {
return 0, fmt.Errorf("tencent im history consumer is required")
}
downloadURL, err := url.Parse(strings.TrimSpace(file.URL))
if err != nil || downloadURL.Scheme != "https" || downloadURL.Host == "" {
return 0, fmt.Errorf("tencent im history download url is invalid")
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL.String(), nil)
if err != nil {
return 0, fmt.Errorf("build tencent im history request: %w", err)
}
downloadClient := *c.httpClient
if downloadClient.Timeout <= 0 || downloadClient.Timeout < historyDownloadTimeout {
downloadClient.Timeout = historyDownloadTimeout
}
response, err := downloadClient.Do(request)
if err != nil {
var transportErr *url.Error
if errors.As(err, &transportErr) {
// url.Error 会把带临时下载签名的完整 URL 拼进错误;只保留底层网络原因。
return 0, fmt.Errorf("tencent im history download failed: %w", transportErr.Err)
}
return 0, fmt.Errorf("tencent im history download failed: %w", err)
}
defer response.Body.Close()
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
return 0, fmt.Errorf("tencent im history download status %d", response.StatusCode)
}
archive, err := gzip.NewReader(response.Body)
if err != nil {
return 0, fmt.Errorf("open tencent im history gzip: %w", err)
}
defer archive.Close()
decoder := json.NewDecoder(archive)
first, err := decoder.Token()
if err != nil {
return 0, fmt.Errorf("decode tencent im history document: %w", err)
}
if delimiter, ok := first.(json.Delim); !ok || delimiter != '{' {
return 0, fmt.Errorf("tencent im history document is not an object")
}
processed := 0
for decoder.More() {
keyToken, err := decoder.Token()
if err != nil {
return processed, fmt.Errorf("decode tencent im history field: %w", err)
}
key, ok := keyToken.(string)
if !ok {
return processed, fmt.Errorf("tencent im history field name is invalid")
}
if key != "MsgList" {
// 元数据字段与统计无关RawMessage 只短暂承载单个标量/小对象,不读取消息正文。
var ignored json.RawMessage
if err := decoder.Decode(&ignored); err != nil {
return processed, fmt.Errorf("skip tencent im history metadata: %w", err)
}
continue
}
listStart, err := decoder.Token()
if err != nil {
return processed, fmt.Errorf("decode tencent im history list: %w", err)
}
if delimiter, ok := listStart.(json.Delim); !ok || delimiter != '[' {
return processed, fmt.Errorf("tencent im history MsgList is not an array")
}
for decoder.More() {
var message C2CHistoryMessage
if err := decoder.Decode(&message); err != nil {
return processed, fmt.Errorf("decode tencent im history message: %w", err)
}
if err := consume(message); err != nil {
return processed, err
}
processed++
}
if _, err := decoder.Token(); err != nil {
return processed, fmt.Errorf("close tencent im history list: %w", err)
}
}
if _, err := decoder.Token(); err != nil {
return processed, fmt.Errorf("close tencent im history document: %w", err)
}
return processed, nil
}

View File

@ -81,6 +81,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "5m" lock_ttl: "5m"
batch_size: 10000 batch_size: 10000
private_message_history:
enabled: true
interval: "1m"
timeout: "2m"
lock_ttl: "3m"
batch_size: 500
game_level_event_relay: game_level_event_relay:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -81,6 +81,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "5m" lock_ttl: "5m"
batch_size: 10000 batch_size: 10000
private_message_history:
enabled: true
interval: "1m"
timeout: "2m"
lock_ttl: "3m"
batch_size: 500
game_level_event_relay: game_level_event_relay:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -81,6 +81,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "5m" lock_ttl: "5m"
batch_size: 10000 batch_size: 10000
private_message_history:
enabled: true
interval: "1m"
timeout: "2m"
lock_ttl: "3m"
batch_size: 500
game_level_event_relay: game_level_event_relay:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -123,6 +123,7 @@ func New(cfg config.Config) (*App, error) {
"admin_user_ban_expiry": userCron.ExpireAdminUserBans, "admin_user_ban_expiry": userCron.ExpireAdminUserBans,
// CP 榜单任务只占 cron 租约并调用 user-service真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。 // CP 榜单任务只占 cron 租约并调用 user-service真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。
"cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard, "cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard,
"private_message_history": userCron.ProcessPrivateMessageHistoryBatch,
"message_fanout": activityCron.ProcessMessageFanoutBatch, "message_fanout": activityCron.ProcessMessageFanoutBatch,
"growth_level_reward": activityCron.ProcessLevelRewardBatch, "growth_level_reward": activityCron.ProcessLevelRewardBatch,
"temporary_growth_level": activityCron.ProcessTemporaryLevelBatch, "temporary_growth_level": activityCron.ProcessTemporaryLevelBatch,

View File

@ -214,6 +214,15 @@ func defaultTasks() map[string]TaskConfig {
LockTTL: "5m", LockTTL: "5m",
BatchSize: 10000, BatchSize: 10000,
}, },
"private_message_history": {
// 腾讯消息文件在小时结束后异步生成;每分钟最多认领一个小时并在 user-service
// 内按 500 条写入,既能回补最近七天,又不会形成下载或数据库突发。
Enabled: true,
Interval: "1m",
Timeout: "2m",
LockTTL: "3m",
BatchSize: 500,
},
"game_level_event_relay": { "game_level_event_relay": {
Enabled: true, Enabled: true,
Interval: "5s", Interval: "5s",

View File

@ -63,6 +63,13 @@ func (c *UserCronClient) RefreshCPIntimacyLeaderboard(ctx context.Context, req s
return userCronResult(resp), err return userCronResult(resp), err
} }
// ProcessPrivateMessageHistoryBatch 只触发 user-service owner 拉取离线小时归档;
// cron 不接触腾讯凭证、下载地址、消息内容或 user 数据库。
func (c *UserCronClient) ProcessPrivateMessageHistoryBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) {
resp, err := c.client.ProcessPrivateMessageHistoryBatch(ctx, userCronRequest(req))
return userCronResult(resp), err
}
func userCronRequest(req scheduler.BatchRequest) *userv1.CronBatchRequest { func userCronRequest(req scheduler.BatchRequest) *userv1.CronBatchRequest {
return &userv1.CronBatchRequest{ return &userv1.CronBatchRequest{
Meta: &userv1.RequestMeta{ Meta: &userv1.RequestMeta{

View File

@ -211,6 +211,23 @@ CREATE TABLE IF NOT EXISTS user_private_message_events (
KEY idx_private_message_target_time (app_code, target_user_id, occurred_at_ms, sender_user_id) KEY idx_private_message_target_time (app_code, target_user_id, occurred_at_ms, sender_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信发送事实,仅保存统计字段'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信发送事实,仅保存统计字段';
CREATE TABLE IF NOT EXISTS user_private_message_history_sync (
sdk_app_id BIGINT NOT NULL COMMENT '腾讯 IM SDKAppID',
hour_start_ms BIGINT NOT NULL COMMENT '待同步 UTC 小时起点 epoch ms',
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/processing/retry/completed',
attempts INT NOT NULL DEFAULT 0 COMMENT '外部归档拉取尝试次数',
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '失败后最早重试时间',
locked_by VARCHAR(96) NOT NULL DEFAULT '' COMMENT '当前 cron run ID',
locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理租约截止时间',
source_message_count INT NOT NULL DEFAULT 0 COMMENT '归档原始消息数',
imported_event_count INT NOT NULL DEFAULT 0 COMMENT '新增 Host 私信事实数',
last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最近一次脱敏错误',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (sdk_app_id, hour_start_ms),
KEY idx_private_message_history_sync_ready (sdk_app_id, status, next_retry_at_ms, hour_start_ms, locked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='腾讯 IM 小时归档离线同步状态';
CREATE TABLE IF NOT EXISTS user_friend_applications ( CREATE TABLE IF NOT EXISTS user_friend_applications (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离',
requester_user_id BIGINT NOT NULL COMMENT '申请人用户 ID', requester_user_id BIGINT NOT NULL COMMENT '申请人用户 ID',

View File

@ -0,0 +1,20 @@
USE hyapp_user;
-- 归档同步表每个 SDKAppID 每小时仅一行cron 只在该小表上 claim
-- 私信事实仍通过 user_id 主键校验租户后写入既有索引表,不扫描用户或消息全表。
CREATE TABLE IF NOT EXISTS user_private_message_history_sync (
sdk_app_id BIGINT NOT NULL COMMENT '腾讯 IM SDKAppID',
hour_start_ms BIGINT NOT NULL COMMENT '待同步 UTC 小时起点 epoch ms',
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/processing/retry/completed',
attempts INT NOT NULL DEFAULT 0 COMMENT '外部归档拉取尝试次数',
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '失败后最早重试时间',
locked_by VARCHAR(96) NOT NULL DEFAULT '' COMMENT '当前 cron run ID',
locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理租约截止时间',
source_message_count INT NOT NULL DEFAULT 0 COMMENT '归档原始消息数',
imported_event_count INT NOT NULL DEFAULT 0 COMMENT '新增 Host 私信事实数',
last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最近一次脱敏错误',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (sdk_app_id, hour_start_ms),
KEY idx_private_message_history_sync_ready (sdk_app_id, status, next_retry_at_ms, hour_start_ms, locked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='腾讯 IM 小时归档离线同步状态';

View File

@ -192,6 +192,7 @@ func New(cfg config.Config) (*App, error) {
} }
var imLoginKicker userservice.IMLoginKicker var imLoginKicker userservice.IMLoginKicker
var imAccountImporter authservice.IMAccountImporter var imAccountImporter authservice.IMAccountImporter
var imHistoryClient hostservice.PrivateMessageHistoryClient
if cfg.TencentIM.Enabled { if cfg.TencentIM.Enabled {
imClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) imClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig())
if err != nil { if err != nil {
@ -207,6 +208,7 @@ func New(cfg config.Config) (*App, error) {
} }
imLoginKicker = imClient imLoginKicker = imClient
imAccountImporter = tencentIMAccountImporter{client: imClient} imAccountImporter = tencentIMAccountImporter{client: imClient}
imHistoryClient = imClient
} }
var roomConn *grpc.ClientConn var roomConn *grpc.ClientConn
var roomEvictor userservice.RoomEvictor var roomEvictor userservice.RoomEvictor
@ -323,6 +325,8 @@ func New(cfg config.Config) (*App, error) {
) )
hostSvc := hostservice.New(hostRepo, hostSvc := hostservice.New(hostRepo,
hostservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)), hostservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)),
// 私聊人数来自腾讯每小时离线归档;该依赖只暴露给 cron RPC不进入 IM 发消息链路。
hostservice.WithPrivateMessageHistorySource(cfg.TencentIM.SDKAppID, imHistoryClient),
) )
// user service 负责用户主状态和 display_user_id/靓号用例,不进入房间高频流程。 // user service 负责用户主状态和 display_user_id/靓号用例,不进入房间高频流程。
userSvc := userservice.New(userRepo, userSvc := userservice.New(userRepo,

View File

@ -0,0 +1,233 @@
package host
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"time"
"hyapp/pkg/tencentim"
"hyapp/pkg/xerr"
hostdomain "hyapp/services/user-service/internal/domain/host"
)
const (
privateMessageHistoryHours = 7 * 24
privateMessageHistoryReadyDelay = 2 * time.Hour
privateMessageHistoryRetryDelay = 10 * time.Minute
privateMessageHistoryImportMax = 500
privateMessageHistoryEmptyMaxWait = 4 * time.Hour
)
// PrivateMessageHistoryClient 是腾讯 IM 离线归档边界;它只在 cron 批处理中调用,
// 不参与客户端发送、接收或 UserSig 签发链路。
type PrivateMessageHistoryClient interface {
ListC2CHistoryFiles(ctx context.Context, hourStartUTC time.Time) ([]tencentim.C2CHistoryFile, error)
StreamC2CHistory(ctx context.Context, file tencentim.C2CHistoryFile, consume func(tencentim.C2CHistoryMessage) error) (int, error)
}
// PrivateMessageHistoryRepository 保存小时同步租约和最小私聊统计事实。
type PrivateMessageHistoryRepository interface {
PreparePrivateMessageHistoryHours(ctx context.Context, sdkAppID int64, fromHourMS int64, throughHourMS int64, nowMS int64) error
ClaimPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, runID string, fromHourMS int64, throughHourMS int64, nowMS int64, lockTTL time.Duration) (hourStartMS int64, attempts int, claimed bool, err error)
ImportPrivateMessageEvents(ctx context.Context, events []hostdomain.PrivateMessageEvent) (int, error)
CompletePrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, sourceMessages int, importedEvents int, nowMS int64) error
RetryPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, retryAtMS int64, errorMessage string, nowMS int64) error
}
// PrivateMessageHistoryBatchResult 汇总一次小时归档处理,不把下载地址或消息正文带到 cron 日志。
type PrivateMessageHistoryBatchResult struct {
Claimed bool
SourceCount int
ImportedCount int
RetryPending bool
}
// WithPrivateMessageHistorySource 挂载腾讯归档和 owner repository。SDKAppID 是共享归档主键,
// 即使 cron 按多个 app_code 并发触发,同一小时也只会被认领一次。
func WithPrivateMessageHistorySource(sdkAppID int64, client PrivateMessageHistoryClient) Option {
return func(s *Service) {
if sdkAppID <= 0 || client == nil {
return
}
repository, ok := s.repository.(PrivateMessageHistoryRepository)
if !ok {
return
}
s.privateMessageHistorySDKAppID = sdkAppID
s.privateMessageHistoryClient = client
s.privateMessageHistoryRepository = repository
}
}
// ProcessPrivateMessageHistoryBatch 处理最近七天中一个已完成的 UTC 小时。
// get_history 文件由腾讯异步生成,因此统计最多延迟约两小时,但不会给 IM 发消息增加等待。
func (s *Service) ProcessPrivateMessageHistoryBatch(ctx context.Context, runID string, lockTTL time.Duration, importBatchSize int) (PrivateMessageHistoryBatchResult, error) {
if s == nil || s.privateMessageHistoryClient == nil || s.privateMessageHistoryRepository == nil || s.privateMessageHistorySDKAppID <= 0 {
return PrivateMessageHistoryBatchResult{}, xerr.New(xerr.Unavailable, "private message history sync is not configured")
}
runID = strings.TrimSpace(runID)
if runID == "" {
return PrivateMessageHistoryBatchResult{}, xerr.New(xerr.InvalidArgument, "run_id is required")
}
if importBatchSize <= 0 || importBatchSize > privateMessageHistoryImportMax {
importBatchSize = privateMessageHistoryImportMax
}
if lockTTL <= 0 {
lockTTL = 5 * time.Minute
}
now := s.now().UTC()
throughHour := now.Truncate(time.Hour).Add(-privateMessageHistoryReadyDelay)
fromHour := throughHour.Add(-time.Duration(privateMessageHistoryHours-1) * time.Hour)
if err := s.privateMessageHistoryRepository.PreparePrivateMessageHistoryHours(
ctx,
s.privateMessageHistorySDKAppID,
fromHour.UnixMilli(),
throughHour.UnixMilli(),
now.UnixMilli(),
); err != nil {
return PrivateMessageHistoryBatchResult{}, err
}
hourStartMS, attempts, claimed, err := s.privateMessageHistoryRepository.ClaimPrivateMessageHistoryHour(
ctx,
s.privateMessageHistorySDKAppID,
runID,
fromHour.UnixMilli(),
throughHour.UnixMilli(),
now.UnixMilli(),
lockTTL,
)
if err != nil || !claimed {
return PrivateMessageHistoryBatchResult{}, err
}
result := PrivateMessageHistoryBatchResult{Claimed: true}
hourStart := time.UnixMilli(hourStartMS).UTC()
files, err := s.privateMessageHistoryClient.ListC2CHistoryFiles(ctx, hourStart)
if err != nil {
if tencentim.IsRESTErrorCode(err, 1004) {
// 1004 同时表示“无消息”和“文件尚未生成”。最近小时先重试;超过生成窗口后
// 视为空小时完成,避免一个永久空小时阻塞七天回补。
if attempts < 3 && now.Sub(hourStart) < privateMessageHistoryEmptyMaxWait {
if retryErr := s.markPrivateMessageHistoryRetry(ctx, hourStartMS, runID, err, now); retryErr != nil {
return result, retryErr
}
result.RetryPending = true
return result, nil
}
if completeErr := s.privateMessageHistoryRepository.CompletePrivateMessageHistoryHour(
ctx, s.privateMessageHistorySDKAppID, hourStartMS, runID, 0, 0, now.UnixMilli(),
); completeErr != nil {
return result, completeErr
}
return result, nil
}
if retryErr := s.markPrivateMessageHistoryRetry(ctx, hourStartMS, runID, err, now); retryErr != nil {
return result, retryErr
}
return result, err
}
pending := make([]hostdomain.PrivateMessageEvent, 0, importBatchSize)
flush := func() error {
if len(pending) == 0 {
return nil
}
imported, importErr := s.privateMessageHistoryRepository.ImportPrivateMessageEvents(ctx, pending)
if importErr != nil {
return importErr
}
result.ImportedCount += imported
pending = pending[:0]
return nil
}
consume := func(message tencentim.C2CHistoryMessage) error {
event, ok := privateMessageEventFromHistory(message)
if !ok {
return nil
}
pending = append(pending, event)
if len(pending) >= importBatchSize {
return flush()
}
return nil
}
for _, file := range files {
processed, streamErr := s.privateMessageHistoryClient.StreamC2CHistory(ctx, file, consume)
result.SourceCount += processed
if streamErr != nil {
if retryErr := s.markPrivateMessageHistoryRetry(ctx, hourStartMS, runID, streamErr, now); retryErr != nil {
return result, retryErr
}
return result, streamErr
}
}
if err := flush(); err != nil {
if retryErr := s.markPrivateMessageHistoryRetry(ctx, hourStartMS, runID, err, now); retryErr != nil {
return result, retryErr
}
return result, err
}
if err := s.privateMessageHistoryRepository.CompletePrivateMessageHistoryHour(
ctx,
s.privateMessageHistorySDKAppID,
hourStartMS,
runID,
result.SourceCount,
result.ImportedCount,
s.now().UTC().UnixMilli(),
); err != nil {
return result, err
}
return result, nil
}
func (s *Service) markPrivateMessageHistoryRetry(ctx context.Context, hourStartMS int64, runID string, cause error, now time.Time) error {
retryErr := s.privateMessageHistoryRepository.RetryPrivateMessageHistoryHour(
ctx,
s.privateMessageHistorySDKAppID,
hourStartMS,
runID,
now.Add(privateMessageHistoryRetryDelay).UnixMilli(),
cause.Error(),
now.UnixMilli(),
)
if retryErr == nil {
return nil
}
return errors.Join(cause, retryErr)
}
func privateMessageEventFromHistory(message tencentim.C2CHistoryMessage) (hostdomain.PrivateMessageEvent, bool) {
senderText := strings.TrimSpace(message.FromAccount)
targetText := strings.TrimSpace(message.ToAccount)
senderUserID, senderErr := strconv.ParseInt(senderText, 10, 64)
targetUserID, targetErr := strconv.ParseInt(targetText, 10, 64)
if senderErr != nil || targetErr != nil || senderUserID <= 0 || targetUserID <= 0 || message.Timestamp <= 0 {
// 共享 SDKAppID 可能包含管理员/第三方非数字 identifier只有项目内部不可变 user_id
// 才能安全映射 users 主键,其余消息不属于 Host Center 统计事实。
return hostdomain.PrivateMessageEvent{}, false
}
identity := fmt.Sprintf(
"%s\x00%s\x00%d\x00%d\x00%d",
senderText,
targetText,
message.Timestamp,
message.Sequence,
message.Random,
)
hash := sha256.Sum256([]byte(identity))
return hostdomain.PrivateMessageEvent{
EventID: "im-history:" + hex.EncodeToString(hash[:]),
SenderUserID: senderUserID,
TargetUserID: targetUserID,
OccurredAtMS: message.Timestamp * int64(time.Second/time.Millisecond),
}, true
}

View File

@ -70,6 +70,10 @@ type IDGenerator interface {
type Service struct { type Service struct {
repository Repository repository Repository
roleScopePolicyRepository RoleScopePolicyRepository roleScopePolicyRepository RoleScopePolicyRepository
// privateMessageHistory* 只用于 cron 离线同步,不进入任何 Host Center 查询或 IM 实时链路。
privateMessageHistoryRepository PrivateMessageHistoryRepository
privateMessageHistoryClient PrivateMessageHistoryClient
privateMessageHistorySDKAppID int64
idGenerator IDGenerator idGenerator IDGenerator
now func() time.Time now func() time.Time
} }

View File

@ -0,0 +1,210 @@
package host
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"hyapp/pkg/xerr"
hostdomain "hyapp/services/user-service/internal/domain/host"
)
const privateMessageHistoryMaxHours = 7 * 24
// PreparePrivateMessageHistoryHours 只向小时状态小表补齐最近七天的主键,不更新已完成行。
// 多个 app_code 的 cron 并发调用会由 INSERT IGNORE 和共享 SDKAppID 主键自然收敛。
func (r *Repository) PreparePrivateMessageHistoryHours(ctx context.Context, sdkAppID int64, fromHourMS int64, throughHourMS int64, nowMS int64) error {
if r == nil || r.db == nil {
return xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
if sdkAppID <= 0 || fromHourMS <= 0 || throughHourMS < fromHourMS {
return xerr.New(xerr.InvalidArgument, "private message history range is invalid")
}
const hourMS = int64(time.Hour / time.Millisecond)
hourCount := int((throughHourMS-fromHourMS)/hourMS) + 1
if hourCount <= 0 || hourCount > privateMessageHistoryMaxHours {
return xerr.New(xerr.InvalidArgument, "private message history range exceeds seven days")
}
values := make([]string, 0, hourCount)
args := make([]any, 0, hourCount*4)
for hourStartMS := fromHourMS; hourStartMS <= throughHourMS; hourStartMS += hourMS {
values = append(values, "(?, ?, 'pending', ?, ?)")
args = append(args, sdkAppID, hourStartMS, nowMS, nowMS)
}
_, err := r.db.ExecContext(ctx, `
INSERT IGNORE INTO user_private_message_history_sync (
sdk_app_id, hour_start_ms, status, created_at_ms, updated_at_ms
) VALUES `+strings.Join(values, ","), args...)
return err
}
// ClaimPrivateMessageHistoryHour 原子认领最新的可处理小时,让当天 Host Center 数据优先恢复。
// 状态表最多保留最近七天新增的小时行ORDER BY/LIMIT 不会触碰消息事实大表。
func (r *Repository) ClaimPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, runID string, fromHourMS int64, throughHourMS int64, nowMS int64, lockTTL time.Duration) (int64, int, bool, error) {
if r == nil || r.db == nil {
return 0, 0, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
runID = strings.TrimSpace(runID)
if sdkAppID <= 0 || runID == "" || fromHourMS <= 0 || throughHourMS < fromHourMS {
return 0, 0, false, xerr.New(xerr.InvalidArgument, "private message history claim is invalid")
}
if lockTTL <= 0 {
lockTTL = 5 * time.Minute
}
lockedUntilMS := nowMS + lockTTL.Milliseconds()
result, err := r.db.ExecContext(ctx, `
UPDATE user_private_message_history_sync
SET status = 'processing',
attempts = attempts + 1,
locked_by = ?,
locked_until_ms = ?,
updated_at_ms = ?
WHERE sdk_app_id = ?
AND hour_start_ms >= ? AND hour_start_ms <= ?
AND (
(status IN ('pending', 'retry') AND next_retry_at_ms <= ?)
OR (status = 'processing' AND locked_until_ms <= ?)
)
ORDER BY hour_start_ms DESC
LIMIT 1`,
runID, lockedUntilMS, nowMS,
sdkAppID, fromHourMS, throughHourMS, nowMS, nowMS,
)
if err != nil {
return 0, 0, false, err
}
affected, err := result.RowsAffected()
if err != nil || affected == 0 {
return 0, 0, false, err
}
var hourStartMS int64
var attempts int
err = r.db.QueryRowContext(ctx, `
SELECT hour_start_ms, attempts
FROM user_private_message_history_sync
WHERE sdk_app_id = ? AND status = 'processing' AND locked_by = ? AND locked_until_ms = ?
LIMIT 1`,
sdkAppID, runID, lockedUntilMS,
).Scan(&hourStartMS, &attempts)
if err != nil {
if err == sql.ErrNoRows {
return 0, 0, false, xerr.New(xerr.Conflict, "private message history claim was lost")
}
return 0, 0, false, err
}
return hourStartMS, attempts, true, nil
}
// ImportPrivateMessageEvents 批量把腾讯归档转换为最小统计事实。users 主键分别验证收发双方,
// app_code 取接收者主数据且要求双方同租户host_profiles 联接避免保存与 Host Center 无关的普通私聊。
func (r *Repository) ImportPrivateMessageEvents(ctx context.Context, events []hostdomain.PrivateMessageEvent) (int, error) {
if r == nil || r.db == nil {
return 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
if len(events) == 0 {
return 0, nil
}
if len(events) > 500 {
return 0, xerr.New(xerr.InvalidArgument, "private message history import batch exceeds 500")
}
createdAtMS := time.Now().UTC().UnixMilli()
selects := make([]string, 0, len(events))
args := make([]any, 0, len(events)*5)
for index, event := range events {
prefix := "SELECT"
if index > 0 {
prefix = "UNION ALL SELECT"
}
selects = append(selects, prefix+" ? AS event_id, ? AS sender_user_id, ? AS target_user_id, ? AS occurred_at_ms, ? AS created_at_ms")
args = append(args, event.EventID, event.SenderUserID, event.TargetUserID, event.OccurredAtMS, createdAtMS)
}
query := `
INSERT IGNORE INTO user_private_message_events (
app_code, event_id, sender_user_id, target_user_id, occurred_at_ms, created_at_ms
)
SELECT target.app_code,
imported.event_id,
sender.user_id,
target.user_id,
imported.occurred_at_ms,
imported.created_at_ms
FROM (` + strings.Join(selects, "\n") + `) imported
INNER JOIN users target
ON target.user_id = imported.target_user_id
INNER JOIN users sender
ON sender.user_id = imported.sender_user_id AND sender.app_code = target.app_code
INNER JOIN host_profiles host
ON host.app_code = target.app_code AND host.user_id = target.user_id`
result, err := r.db.ExecContext(ctx, query, args...)
if err != nil {
return 0, fmt.Errorf("import private message history events: %w", err)
}
affected, err := result.RowsAffected()
return int(affected), err
}
// CompletePrivateMessageHistoryHour 仅允许当前 run 完成自己的租约,防止超时旧 worker
// 覆盖新 worker 的重试结果。
func (r *Repository) CompletePrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, sourceMessages int, importedEvents int, nowMS int64) error {
result, err := r.db.ExecContext(ctx, `
UPDATE user_private_message_history_sync
SET status = 'completed',
source_message_count = ?,
imported_event_count = ?,
next_retry_at_ms = 0,
locked_by = '',
locked_until_ms = 0,
last_error = '',
updated_at_ms = ?
WHERE sdk_app_id = ? AND hour_start_ms = ? AND status = 'processing' AND locked_by = ?`,
sourceMessages, importedEvents, nowMS, sdkAppID, hourStartMS, strings.TrimSpace(runID),
)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return xerr.New(xerr.Conflict, "private message history completion lease was lost")
}
return nil
}
// RetryPrivateMessageHistoryHour 记录脱敏错误并延后重试;下载 URL 和 UserSig 已在 REST
// 边界被剥离last_error 仍限制长度,避免外部响应放大状态表。
func (r *Repository) RetryPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, retryAtMS int64, errorMessage string, nowMS int64) error {
errorMessage = strings.TrimSpace(errorMessage)
if len(errorMessage) > 512 {
errorMessage = errorMessage[:512]
}
result, err := r.db.ExecContext(ctx, `
UPDATE user_private_message_history_sync
SET status = 'retry',
next_retry_at_ms = ?,
locked_by = '',
locked_until_ms = 0,
last_error = ?,
updated_at_ms = ?
WHERE sdk_app_id = ? AND hour_start_ms = ? AND status = 'processing' AND locked_by = ?`,
retryAtMS, errorMessage, nowMS, sdkAppID, hourStartMS, strings.TrimSpace(runID),
)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return xerr.New(xerr.Conflict, "private message history retry lease was lost")
}
return nil
}

View File

@ -162,6 +162,40 @@ func (s *Server) RefreshCPIntimacyLeaderboard(ctx context.Context, req *userv1.C
}, nil }, nil
} }
// ProcessPrivateMessageHistoryBatch 从腾讯 IM 小时归档恢复 Host 私聊发送人数事实。
// 任务只在 cron 后台运行;即使腾讯归档延迟或下载失败,也不会影响客户端私聊发送。
func (s *Server) ProcessPrivateMessageHistoryBatch(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta())
if s.hostSvc == nil {
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
}
result, err := s.hostSvc.ProcessPrivateMessageHistoryBatch(
ctx,
req.GetRunId(),
durationFromMillis(req.GetLockTtlMs()),
int(req.GetBatchSize()),
)
if err != nil {
return nil, xerr.ToGRPCError(err)
}
claimed := int32(0)
if result.Claimed {
claimed = 1
}
failureCount := int32(0)
if result.RetryPending {
failureCount = 1
}
return &userv1.CronBatchResponse{
ClaimedCount: claimed,
ProcessedCount: int32(result.SourceCount),
SuccessCount: int32(result.ImportedCount),
FailureCount: failureCount,
// 小时归档有意按 cron interval 节流;回补不通过 has_more 形成无间隔下载突发。
HasMore: false,
}, nil
}
func durationFromMillis(value int64) time.Duration { func durationFromMillis(value int64) time.Duration {
if value <= 0 { if value <= 0 {
return 0 return 0