fix: sync host private messages from IM history
This commit is contained in:
parent
42e4adab56
commit
180d7ecd8b
@ -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
|
||||||
|
|||||||
@ -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 的绑定和失效。
|
||||||
|
|||||||
@ -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
176
pkg/tencentim/history.go
Normal 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
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 小时归档离线同步状态';
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user