From 87807e3523ae109ccc8d5632eb2f249c35c35689 Mon Sep 17 00:00:00 2001 From: wangyue Date: Sat, 11 May 2024 16:17:32 +0800 Subject: [PATCH] Push message (#6) (#8) 1.send all message push 2.delete expired tokens 3.delete invalid tokens --- .../Commons/ExpiredDeviceCriteria.cs | 7 + .../Commons/InvalidDeviceCriteria.cs | 32 +++++ .../DeviceInfo/Dtos/ReportAppStatusDto.cs | 4 +- .../DeviceInfo/Dtos/UserDeviceInfoDto.cs | 2 + .../MessagePush.Application.Contracts.csproj | 1 + .../Common/DateTimeExtensions.cs | 11 ++ .../Common/UnreadMessageHelper.cs | 13 +- .../DeviceInfo/Provider/UserDeviceProvider.cs | 110 +++++++-------- .../DeviceInfo/UserDeviceAppService.cs | 28 +++- .../MessagePush.Application.csproj | 1 + .../MessagePush/MessagePushAppService.cs | 130 +++++++----------- .../Provider/MessagePushProvider.cs | 106 ++++++++++++-- .../MessagePushApplicationModule.cs | 10 +- .../Options/MessagePushOptions.cs | 35 +++++ .../Options/ScheduledTasksOptions.cs | 56 ++++++++ .../Redis/RedisClient.cs | 43 ++++++ .../Jobs/DeleteExpiredDeviceInfoJob.cs | 66 +++++++++ .../ScheduledTasks/QuartzJobFactory.cs | 25 ++++ .../ScheduledTasks/QuartzStartup.cs | 63 +++++++++ .../MessagePush.DbMigrator.csproj | 1 + .../MessagePush.Domain.Shared.csproj | 1 + .../Entities/Es/UnreadMessageIndex.cs | 19 --- .../Entities/Redis/UnreadMessage.cs | 14 ++ .../MessagePush.Domain.csproj | 1 + ...MessagePush.EntityEventHandler.Core.csproj | 4 + .../MessagePush.EntityEventHandler.csproj | 1 + .../MessagePush.Grains.csproj | 1 + .../MessagePush.HttpApi.Client.csproj | 1 + .../MessagePush.HttpApi.Host.csproj | 1 + src/MessagePush.HttpApi.Host/Program.cs | 4 + src/MessagePush.HttpApi.Host/appsettings.json | 10 ++ .../MessagePush.HttpApi.csproj | 1 + .../MessagePush.MongoDB.csproj | 1 + src/MessagePush.Silo/MessagePush.Silo.csproj | 1 + .../MessagePush.Application.Tests.csproj | 1 + .../MessagePush.Domain.Tests.csproj | 1 + .../MessagePush.Grain.Tests.csproj | 1 + .../MessagePush.MongoDB.Tests.csproj | 1 + .../MessagePush.Orleans.TestBase.csproj | 1 + .../MessagePush.TestBase.csproj | 1 + 40 files changed, 634 insertions(+), 176 deletions(-) create mode 100644 src/MessagePush.Application.Contracts/Commons/ExpiredDeviceCriteria.cs create mode 100644 src/MessagePush.Application.Contracts/Commons/InvalidDeviceCriteria.cs create mode 100644 src/MessagePush.Application/Common/DateTimeExtensions.cs create mode 100644 src/MessagePush.Application/Options/MessagePushOptions.cs create mode 100644 src/MessagePush.Application/Options/ScheduledTasksOptions.cs create mode 100644 src/MessagePush.Application/Redis/RedisClient.cs create mode 100644 src/MessagePush.Application/ScheduledTasks/Jobs/DeleteExpiredDeviceInfoJob.cs create mode 100644 src/MessagePush.Application/ScheduledTasks/QuartzJobFactory.cs create mode 100644 src/MessagePush.Application/ScheduledTasks/QuartzStartup.cs delete mode 100644 src/MessagePush.Domain/Entities/Es/UnreadMessageIndex.cs create mode 100644 src/MessagePush.Domain/Entities/Redis/UnreadMessage.cs diff --git a/src/MessagePush.Application.Contracts/Commons/ExpiredDeviceCriteria.cs b/src/MessagePush.Application.Contracts/Commons/ExpiredDeviceCriteria.cs new file mode 100644 index 0000000..e767e7f --- /dev/null +++ b/src/MessagePush.Application.Contracts/Commons/ExpiredDeviceCriteria.cs @@ -0,0 +1,7 @@ +namespace MessagePush.Commons; + +public class ExpiredDeviceCriteria +{ + public int FromDays { get; set; } + public int Limit { get; set; } +} \ No newline at end of file diff --git a/src/MessagePush.Application.Contracts/Commons/InvalidDeviceCriteria.cs b/src/MessagePush.Application.Contracts/Commons/InvalidDeviceCriteria.cs new file mode 100644 index 0000000..4e3ad8a --- /dev/null +++ b/src/MessagePush.Application.Contracts/Commons/InvalidDeviceCriteria.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using MessagePush.DeviceInfo.Dtos; + +namespace MessagePush.Commons; + +public class InvalidDeviceCriteria +{ + public string UserId { get; set; } + public List LoginUserIds { get; set; } + public string DeviceId { get; set; } + + public static InvalidDeviceCriteria FromUserDeviceInfoDto(UserDeviceInfoDto input) + { + return new InvalidDeviceCriteria + { + UserId = input.UserId, + LoginUserIds = input.LoginUserIds ?? new List(), + DeviceId = input.DeviceId + }; + } + + public static InvalidDeviceCriteria FromReportAppStatusDto(ReportAppStatusDto input) + { + return new InvalidDeviceCriteria + { + UserId = input.UserId, + LoginUserIds = input.LoginUserIds ?? new List(), + DeviceId = input.DeviceId + }; + } +} \ No newline at end of file diff --git a/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/ReportAppStatusDto.cs b/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/ReportAppStatusDto.cs index 442cc34..1778e91 100644 --- a/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/ReportAppStatusDto.cs +++ b/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/ReportAppStatusDto.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace MessagePush.DeviceInfo.Dtos; @@ -6,7 +7,8 @@ public class ReportAppStatusDto { [Required] public string UserId { get; set; } [Required] public string DeviceId { get; set; } - [Required] public AppStatus Status { get; set; } + [Required] public AppStatus Status { get; set; } [Required] public NetworkType NetworkType { get; set; } public int UnreadCount { get; set; } + public List LoginUserIds { get; set; } } \ No newline at end of file diff --git a/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/UserDeviceInfoDto.cs b/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/UserDeviceInfoDto.cs index 0440cc3..31a5e35 100644 --- a/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/UserDeviceInfoDto.cs +++ b/src/MessagePush.Application.Contracts/DeviceInfo/Dtos/UserDeviceInfoDto.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace MessagePush.DeviceInfo.Dtos; @@ -10,4 +11,5 @@ public class UserDeviceInfoDto public long RefreshTime { get; set; } [Required] public NetworkType NetworkType { get; set; } public DeviceInfoDto DeviceInfo { get; set; } + public List LoginUserIds { get; set; } } \ No newline at end of file diff --git a/src/MessagePush.Application.Contracts/MessagePush.Application.Contracts.csproj b/src/MessagePush.Application.Contracts/MessagePush.Application.Contracts.csproj index 1b293b6..a0bf7a0 100644 --- a/src/MessagePush.Application.Contracts/MessagePush.Application.Contracts.csproj +++ b/src/MessagePush.Application.Contracts/MessagePush.Application.Contracts.csproj @@ -24,6 +24,7 @@ + diff --git a/src/MessagePush.Application/Common/DateTimeExtensions.cs b/src/MessagePush.Application/Common/DateTimeExtensions.cs new file mode 100644 index 0000000..d99ff64 --- /dev/null +++ b/src/MessagePush.Application/Common/DateTimeExtensions.cs @@ -0,0 +1,11 @@ +using System; + +namespace MessagePush.Common; + +public static class DateTimeExtensions +{ + public static DateTime SubtractDays(this DateTime date, int days) + { + return date.AddDays(-days); + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/Common/UnreadMessageHelper.cs b/src/MessagePush.Application/Common/UnreadMessageHelper.cs index 859830e..6a442d9 100644 --- a/src/MessagePush.Application/Common/UnreadMessageHelper.cs +++ b/src/MessagePush.Application/Common/UnreadMessageHelper.cs @@ -3,6 +3,7 @@ using System.Linq; using MessagePush.DeviceInfo; using MessagePush.Entities.Es; +using MessagePush.Entities.Redis; using Volo.Abp; namespace MessagePush.Common; @@ -19,18 +20,18 @@ public static string GetId(string userId, string messageType) return $"{userId}-{messageType}"; } - public static int GetUnreadCount(List unreadMessageInfos) + public static int GetUnreadCount(UnreadMessage unreadMessages) { - if (unreadMessageInfos.IsNullOrEmpty()) return 0; + if (unreadMessages == null) return 0; - return unreadMessageInfos.Select(t => t.UnreadCount).Sum(); + return unreadMessages.UnreadCount; } - public static int GetUnreadCount(List unreadMessageInfos, MessageType messageType) + public static int GetUnreadCount(List unreadMessages, MessageType messageType) { - if (unreadMessageInfos.IsNullOrEmpty()) return 0; + if (unreadMessages.IsNullOrEmpty()) return 0; - var count = unreadMessageInfos + var count = unreadMessages .FirstOrDefault(t => t.MessageType.Equals(messageType.ToString(), StringComparison.OrdinalIgnoreCase)) ?.UnreadCount; diff --git a/src/MessagePush.Application/DeviceInfo/Provider/UserDeviceProvider.cs b/src/MessagePush.Application/DeviceInfo/Provider/UserDeviceProvider.cs index e3818c9..1f4d181 100644 --- a/src/MessagePush.Application/DeviceInfo/Provider/UserDeviceProvider.cs +++ b/src/MessagePush.Application/DeviceInfo/Provider/UserDeviceProvider.cs @@ -5,7 +5,9 @@ using AElf.Indexing.Elasticsearch; using MessagePush.Commons; using MessagePush.Entities.Es; +using MessagePush.Entities.Redis; using MessagePush.MessagePush.Provider; +using MessagePush.Redis; using Nest; using Volo.Abp.DependencyInjection; @@ -15,23 +17,23 @@ public interface IUserDeviceProvider { Task GetDeviceInfoAsync(string id); Task DeleteUserDeviceAsync(string id); - Task GetUnreadInfoAsync(string userId); Task UpdateUnreadInfoAsync(string appId, string userId, int unreadCount); + Task> GetInvalidDeviceInfos(InvalidDeviceCriteria criteria); + Task> GetExpiredDeviceInfos(ExpiredDeviceCriteria criteria); } public class UserDeviceProvider : IUserDeviceProvider, ISingletonDependency { private readonly INESTRepository _userDeviceRepository; - private readonly INESTRepository _unreadMessageIndexRepository; private readonly IMessagePushProvider _messagePushProvider; + private readonly RedisClient _redisClient; public UserDeviceProvider(INESTRepository userDeviceRepository, - INESTRepository unreadMessageIndexRepository, - IMessagePushProvider messagePushProvider) + IMessagePushProvider messagePushProvider, RedisClient redisClient) { _userDeviceRepository = userDeviceRepository; - _unreadMessageIndexRepository = unreadMessageIndexRepository; _messagePushProvider = messagePushProvider; + _redisClient = redisClient; } public async Task GetDeviceInfoAsync(string id) @@ -48,85 +50,75 @@ public async Task DeleteUserDeviceAsync(string id) await _userDeviceRepository.DeleteAsync(id); } - public async Task GetUnreadInfoAsync(string userId) + public async Task GetUnreadInfoAsync(string userId) { - var mustQuery = new List, QueryContainer>>() + return await Task.Run(() => { - descriptor => descriptor.Term(i => i.Field(f => f.Id).Value(userId)) - }; + var unreadMessage = new UnreadMessage() + { + UserId = userId, + AppId = "PortKey", + MessageType = MessageType.RelationOne.ToString() + }; - QueryContainer Filter(QueryContainerDescriptor f) => f.Bool(b => b.Must(mustQuery)); - return await _unreadMessageIndexRepository.GetAsync(Filter); + var unreadCount = _redisClient.Get(unreadMessage.GetKey()); + unreadMessage.UnreadCount = unreadCount; + return unreadMessage; + }); } public async Task UpdateUnreadInfoAsync(string appId, string userId, int unreadCount) { - var unreadInfo = await GetUnreadInfoAsync(userId); + var unreadMessage = await GetUnreadInfoAsync(userId); // await CheckClearMessageAsync(unreadInfo, unreadCount, userId); - if (unreadInfo == null) + if (unreadMessage == null) { - unreadInfo = new UnreadMessageIndex() + unreadMessage = new UnreadMessage() { - Id = userId, UserId = userId, AppId = appId, - UnreadMessageInfos = new List() - }; - } - - if (unreadInfo.UnreadMessageInfos.IsNullOrEmpty()) - { - unreadInfo.UnreadMessageInfos = new List - { - new UnreadMessageInfo - { - MessageType = MessageType.RelationOne.ToString(), - UnreadCount = 0 - } - }; - - await _unreadMessageIndexRepository.AddOrUpdateAsync(unreadInfo); - return; - } - - var imMessage = unreadInfo.UnreadMessageInfos.FirstOrDefault(t => - t.MessageType.Equals(MessageType.RelationOne.ToString(), StringComparison.OrdinalIgnoreCase)); - if (imMessage == null) - { - unreadInfo.UnreadMessageInfos.Add(new UnreadMessageInfo() - { MessageType = MessageType.RelationOne.ToString(), - UnreadCount = unreadCount - }); + UnreadCount = unreadCount, + }; + _redisClient.AddIfNotExists(unreadMessage.GetKey(), unreadMessage.UnreadCount); } else { - imMessage.UnreadCount = unreadCount; + unreadMessage.UnreadCount = unreadCount; } - await _unreadMessageIndexRepository.AddOrUpdateAsync(unreadInfo); + _redisClient.Set(unreadMessage.GetKey(), unreadMessage.UnreadCount); } - private async Task CheckClearMessageAsync(UnreadMessageIndex unreadInfo, int unreadCount, string userId) + public async Task> GetInvalidDeviceInfos(InvalidDeviceCriteria criteria) { - if (unreadInfo == null || unreadInfo.UnreadMessageInfos.IsNullOrEmpty()) + var filter = new Func, QueryContainer>(q => { - return; - } + var baseQuery = q.Term(t => t.Field(f => f.DeviceId).Value(criteria.DeviceId)) && + !q.Terms(t => t.Field(f => f.UserId).Terms(criteria.LoginUserIds.ToArray())); - if (unreadInfo.UnreadMessageInfos.Sum(t => t.UnreadCount) > 0 && unreadCount == 0) - { - //clear message of ios and extension - var userDevices = - await _messagePushProvider.GetUserDevicesAsync(new List() { userId }, string.Empty); + return baseQuery; + }); - var androidDevices = userDevices.Where(t => - t.DeviceInfo.DeviceType.Equals(DeviceType.Android.ToString(), StringComparison.OrdinalIgnoreCase) == - false).ToList(); + var result = await _userDeviceRepository.GetListAsync(filter); + return result.Item2; + } - await _messagePushProvider.BulkPushAsync(androidDevices, string.Empty, CommonConstant.DefaultTitle, - CommonConstant.DefaultContent, new Dictionary(), badge: 0); - } + public async Task> GetExpiredDeviceInfos(ExpiredDeviceCriteria criteria) + { + var filter = new Func, QueryContainer>(q => + q.DateRange(r => r + .Field(f => f.ModificationTime) + .LessThan(DateMath.Now.Subtract(TimeSpan.FromDays(criteria.FromDays))) + ) + ); + + var sort = new Func, IPromise>>(s => + s.Ascending(f => f.ModificationTime) + ); + + var result = await _userDeviceRepository.GetSortListAsync(filter, sortFunc: sort, limit: criteria.Limit); + return result.Item2; } } \ No newline at end of file diff --git a/src/MessagePush.Application/DeviceInfo/UserDeviceAppService.cs b/src/MessagePush.Application/DeviceInfo/UserDeviceAppService.cs index 8701378..90566cb 100644 --- a/src/MessagePush.Application/DeviceInfo/UserDeviceAppService.cs +++ b/src/MessagePush.Application/DeviceInfo/UserDeviceAppService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AElf.Indexing.Elasticsearch; using MessagePush.Commons; @@ -39,8 +40,10 @@ public async Task ReportDeviceInfoAsync(UserDeviceInfoDto input) await _deviceInfoRepository.AddOrUpdateAsync(deviceInfo); Logger.LogDebug("report device info, appId: {appId}, id: {id}", deviceInfo.AppId ?? string.Empty, id); + + TryDeleteInvalidDeviceInfo(InvalidDeviceCriteria.FromUserDeviceInfoDto(input)); } - + public async Task ReportAppStatusAsync(ReportAppStatusDto input) { var id = DeviceInfoHelper.GetId(input.UserId, input.DeviceId, input.NetworkType.ToString()); @@ -58,6 +61,29 @@ public async Task ReportAppStatusAsync(ReportAppStatusDto input) await _deviceInfoRepository.AddOrUpdateAsync(deviceInfo); Logger.LogDebug("report app status, appId: {appId}, id: {id}, status: {status}", deviceInfo.AppId ?? string.Empty, id, deviceInfo.AppStatus); + + TryDeleteInvalidDeviceInfo(InvalidDeviceCriteria.FromReportAppStatusDto(input)); + } + + + private async void TryDeleteInvalidDeviceInfo(InvalidDeviceCriteria criteria) + { + + if (criteria.LoginUserIds == null || !criteria.LoginUserIds.Any()) + { + return; + } + + var invalidDeviceInfos = await _userDeviceProvider.GetInvalidDeviceInfos(criteria); + if (invalidDeviceInfos != null && invalidDeviceInfos.Any()) + { + Logger.LogDebug("Invalid device attempts exist, trying to delete them."); + foreach (var invalidDeviceInfo in invalidDeviceInfos) + { + await _userDeviceProvider.DeleteUserDeviceAsync(invalidDeviceInfo.Id); + Logger.LogDebug("delete invalid device info, id: {id}, invalidDeviceInfo: {invalidDeviceInfo}", invalidDeviceInfo.Id, JsonConvert.SerializeObject(invalidDeviceInfo)); + } + } } public async Task ExitWalletAsync(ExitWalletDto input) diff --git a/src/MessagePush.Application/MessagePush.Application.csproj b/src/MessagePush.Application/MessagePush.Application.csproj index 5a5a078..49b989b 100644 --- a/src/MessagePush.Application/MessagePush.Application.csproj +++ b/src/MessagePush.Application/MessagePush.Application.csproj @@ -15,6 +15,7 @@ + diff --git a/src/MessagePush.Application/MessagePush/MessagePushAppService.cs b/src/MessagePush.Application/MessagePush/MessagePushAppService.cs index 083a532..bf8aecf 100644 --- a/src/MessagePush.Application/MessagePush/MessagePushAppService.cs +++ b/src/MessagePush.Application/MessagePush/MessagePushAppService.cs @@ -2,15 +2,18 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AElf.Indexing.Elasticsearch; using MessagePush.Common; using MessagePush.Commons; using MessagePush.DeviceInfo; using MessagePush.Entities.Es; +using MessagePush.Entities.Redis; using MessagePush.MessagePush.Dtos; using MessagePush.MessagePush.Provider; +using MessagePush.Options; +using MessagePush.Redis; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Nest; using Newtonsoft.Json; using Volo.Abp; @@ -22,16 +25,17 @@ namespace MessagePush.MessagePush; public class MessagePushAppService : MessagePushBaseService, IMessagePushAppService { private readonly IMessagePushProvider _messagePushProvider; - private readonly INESTRepository _unreadMessageIndexRepository; private readonly IHttpContextAccessor _httpContextAccessor; + private readonly MessagePushOptions _messagePushOptions; + private readonly RedisClient _redisClient; public MessagePushAppService(IMessagePushProvider messagePushProvider, - INESTRepository unreadMessageIndexRepository, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, IOptionsSnapshot messagePushOptions, RedisClient redisClient) { _messagePushProvider = messagePushProvider; - _unreadMessageIndexRepository = unreadMessageIndexRepository; _httpContextAccessor = httpContextAccessor; + _messagePushOptions = messagePushOptions.Value; + _redisClient = redisClient; } public async Task PushMessageAsync(MessagePushDto input) @@ -40,16 +44,17 @@ public async Task PushMessageAsync(MessagePushDto input) var userDevices = await _messagePushProvider.GetUserDevicesAsync(input.UserIds, appId); userDevices = userDevices?.Where(t => !t.AppStatus.Equals(AppStatus.Foreground.ToString(), StringComparison.OrdinalIgnoreCase) && - t.ModificationTime > DateTime.Now.AddMonths(-2)).ToList(); + t.ModificationTime > DateTime.Now.SubtractDays(_messagePushOptions.ExpiredDeviceInfoFromDays)).ToList(); if (userDevices.IsNullOrEmpty()) return; - var userIds = userDevices.Select(t => t.UserId).ToList(); - var unreadMessageInfos = await UpdateUnreadCount(userIds); - - var handleAndroidDevicesTask = HandleAndroidDevicesAsync(userDevices, input); - var handleAppleDevicesTask = HandleAppleDevicesAsync(userDevices, unreadMessageInfos, input); - var handleExtensionDevicesTask = HandleExtensionDevicesAsync(userDevices, unreadMessageInfos, input); + var userIds = userDevices.Select(t => t.UserId).Distinct().ToList(); + // var unreadMessageInfos = await UpdateUnreadCount(userIds); + var unreadMessages = await UpdateUnreadCountAsync(userIds); + var handleAndroidDevicesTask = HandleAndroidDevicesAsync(userDevices, input); + var handleAppleDevicesTask = HandleAppleDevicesAsync(userDevices, unreadMessages, input); + var handleExtensionDevicesTask = HandleExtensionDevicesAsync(userDevices, unreadMessages, input); + await Task.WhenAll(handleAndroidDevicesTask, handleAppleDevicesTask, handleExtensionDevicesTask); } @@ -61,67 +66,42 @@ await _messagePushProvider.PushAsync(userDevice.Id, userDevice.RegistrationToken CommonConstant.DefaultContent, input.Data, badge: 0); } - private async Task> GetUnreadInfosAsync(List userIds) + private List GetUnreadMessagesAsync(List userIds) { - var mustQuery = new List, QueryContainer>>() - { - descriptor => descriptor.Terms(i => i.Field(f => f.Id).Terms(userIds)) - }; + List unreadMessages = new List(); - QueryContainer Filter(QueryContainerDescriptor f) => f.Bool(b => b.Must(mustQuery)); - var result = await _unreadMessageIndexRepository.GetListAsync(Filter); - - return result.Item2; - } - - private async Task> UpdateUnreadCount(List userIds) - { - try + if (userIds != null && userIds.Any()) { - var unreadInfos = await GetUnreadInfosAsync(userIds); - - foreach (var unreadInfo in unreadInfos) + foreach (var userId in userIds) { - if (unreadInfo.UnreadMessageInfos.IsNullOrEmpty()) - { - unreadInfo.UnreadMessageInfos = new List + var unreadMessage = new UnreadMessage() { - new UnreadMessageInfo - { - MessageType = MessageType.RelationOne.ToString(), - UnreadCount = 1 - } + UserId = userId, + AppId = "PortKey", + MessageType = MessageType.RelationOne.ToString() }; - - continue; - } - - var imMessage = unreadInfo.UnreadMessageInfos.FirstOrDefault(t => - t.MessageType.Equals(MessageType.RelationOne.ToString(), StringComparison.OrdinalIgnoreCase)); - if (imMessage == null) - { - unreadInfo.UnreadMessageInfos.Add(new UnreadMessageInfo() - { - MessageType = MessageType.RelationOne.ToString(), - UnreadCount = 1 - }); - } - - imMessage.UnreadCount++; + var value = _redisClient.IncrementAndGet(unreadMessage.GetKey()); + unreadMessage.UnreadCount = value; + unreadMessages.Add(unreadMessage); } + } + + return unreadMessages; + } - if (!unreadInfos.IsNullOrEmpty()) + private async Task> UpdateUnreadCountAsync(List userIds) + { + var unreadMessagesAsync = GetUnreadMessagesAsync(userIds); + if (unreadMessagesAsync != null && unreadMessagesAsync.Any()) + { + foreach (var unreadMessage in unreadMessagesAsync) { - await _unreadMessageIndexRepository.BulkAddOrUpdateAsync(unreadInfos); + var json = JsonConvert.SerializeObject(unreadMessage); + Logger.LogInformation("unreadMessage: {json}", json); } - - return unreadInfos; - } - catch (Exception e) - { - Logger.LogError(e, "param: {data}", JsonConvert.SerializeObject(userIds)); - return new List(); } + + return unreadMessagesAsync; } private string GetAppId() @@ -145,29 +125,18 @@ private async Task HandleAndroidDevicesAsync(List userDevices, } private async Task HandleAppleDevicesAsync(List userDevices, - List unreadMessageInfos, MessagePushDto input) + List unreadMessages, MessagePushDto input) { // ios users - var iosTokenInfos = userDevices - .Where(t => t.DeviceInfo.DeviceType.Equals(DeviceType.IOS.ToString(), StringComparison.OrdinalIgnoreCase)) - .Select(t => new { t.Id, t.UserId, t.RegistrationToken }).ToList(); - - var pushTasks = iosTokenInfos.Select(tokenInfo => - { - var unreadMessage = unreadMessageInfos.FirstOrDefault(t => t.UserId == tokenInfo.UserId) - ?.UnreadMessageInfos; - var badge = UnreadMessageHelper.GetUnreadCount(unreadMessage); + var iosDevices = userDevices + .Where(t => t.DeviceInfo.DeviceType.Equals(DeviceType.IOS.ToString(), StringComparison.OrdinalIgnoreCase)).ToList(); - return _messagePushProvider.PushAsync(tokenInfo.Id, tokenInfo.RegistrationToken, input.Icon, input.Title, input.Content, - input.Data, - badge); - }); - - await Task.WhenAll(pushTasks); + await _messagePushProvider.SendAllAsync(iosDevices, input.Icon, input.Title, input.Content, + input.Data, unreadMessages); } private async Task HandleExtensionDevicesAsync(List userDevices, - List unreadMessageInfos, MessagePushDto input) + List unreadMessages, MessagePushDto input) { var extensionDevices = userDevices .Where(t => t.DeviceInfo.DeviceType.Equals(DeviceType.Extension.ToString(), @@ -176,8 +145,7 @@ private async Task HandleExtensionDevicesAsync(List userDevices var pushTasks = extensionDevices.Select(tokenInfo => { - var unreadMessage = unreadMessageInfos.FirstOrDefault(t => t.UserId == tokenInfo.UserId) - ?.UnreadMessageInfos; + var unreadMessage = unreadMessages.FirstOrDefault(t => t.UserId == tokenInfo.UserId); var badge = UnreadMessageHelper.GetUnreadCount(unreadMessage); Logger.LogInformation("push to extension, count: {count}", extensionDevices.Count); diff --git a/src/MessagePush.Application/MessagePush/Provider/MessagePushProvider.cs b/src/MessagePush.Application/MessagePush/Provider/MessagePushProvider.cs index 61875d9..923cfdd 100644 --- a/src/MessagePush.Application/MessagePush/Provider/MessagePushProvider.cs +++ b/src/MessagePush.Application/MessagePush/Provider/MessagePushProvider.cs @@ -4,9 +4,14 @@ using System.Threading.Tasks; using AElf.Indexing.Elasticsearch; using FirebaseAdmin.Messaging; +using MessagePush.Common; using MessagePush.Commons; +using MessagePush.DeviceInfo; using MessagePush.Entities.Es; +using MessagePush.Entities.Redis; +using MessagePush.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Nest; using Newtonsoft.Json; @@ -19,23 +24,40 @@ public interface IMessagePushProvider Task> GetUserDevicesAsync(List userIds, string appId); Task GetUserDeviceAsync(string userId, string deviceId, string appId); - Task BulkPushAsync(List userDevice, string icon, string title, string content, + Task BulkPushAsync(List userDevices, string icon, string title, string content, Dictionary data, int badge = 1); Task PushAsync(string indexId, string token, string icon, string title, string content, Dictionary data, int badge = 1); + + /// + /// Sends a notification message to a list of user devices. + /// This method was originally designed to support all user devices, including Android phones, iPhones, and desktop browsers. + /// However, during actual testing, it was found that the FCM batch send notification message interface it relies on does not support desktop browsers. + /// + /// The list of user devices to which the notification message will be sent. + /// The icon of the notification message. + /// The title of the notification message. + /// The content of the notification message. + /// The data of the notification message. + /// The list of unread message information. + /// A representing the asynchronous operation. + Task SendAllAsync(List userDevices, string icon, string title, string content, + Dictionary data, List unreadMessages); } public class MessagePushProvider : IMessagePushProvider, ISingletonDependency { private readonly INESTRepository _userDeviceRepository; private readonly ILogger _logger; + private readonly MessagePushOptions _messagePushOptions; public MessagePushProvider(INESTRepository userDeviceRepository, - ILogger logger) + ILogger logger, IOptionsSnapshot messagePushOptions) { _userDeviceRepository = userDeviceRepository; _logger = logger; + _messagePushOptions = messagePushOptions.Value; } public async Task> GetUserDevicesAsync(List userIds, string appId) @@ -62,10 +84,10 @@ public async Task GetUserDeviceAsync(string userId, string devi return await _userDeviceRepository.GetAsync(Filter); } - public async Task BulkPushAsync(List userDevice, string icon, string title, string content, + public async Task BulkPushAsync(List userDevices, string icon, string title, string content, Dictionary data, int badge = 1) { - var tokens = userDevice.Select(t => t.RegistrationToken).ToList(); + var tokens = userDevices.Select(t => t.RegistrationToken).ToList(); try { if (tokens.IsNullOrEmpty()) return; @@ -93,7 +115,7 @@ public async Task BulkPushAsync(List userDevice, string icon, s title, content, result.SuccessCount); - TryHandleExceptionAsync(userDevice, result); + TryHandleExceptionAsync(userDevices, result); } catch (Exception e) { @@ -138,15 +160,83 @@ public async Task PushAsync(string indexId, string token, string icon, string ti _ = HandleExceptionAsync(e.Message, indexId, token); } } - - private void TryHandleExceptionAsync(List userDevice, BatchResponse batchResponse) + + public async Task SendAllAsync(List userDevices, string icon, string title, string content, + Dictionary data, List unreadMessages) + { + var messages = new List(); + + foreach (var deviceType in Enum.GetValues(typeof(DeviceType)).Cast()) + { + var devicesOfType = userDevices.Where(t => + t.DeviceInfo.DeviceType.Equals(deviceType.ToString(), StringComparison.OrdinalIgnoreCase)).ToList(); + + if (devicesOfType.Any()) + { + foreach (var device in devicesOfType) + { + var unreadMessage = unreadMessages.FirstOrDefault(t => t.UserId == device.UserId); + var badge = UnreadMessageHelper.GetUnreadCount(unreadMessage); + var message = CreateMessage(device, icon, title, content, data, badge, deviceType); + messages.Add(message); + } + } + } + + if (messages.Any()) + { + int batchSize = _messagePushOptions.SendAllBatchSize; + var batchCount = (int)Math.Ceiling((double)messages.Count / batchSize); + + for (int i = 0; i < batchCount; i++) + { + var batchMessages = messages.Skip(i * batchSize).Take(batchSize).ToList(); + var result = await FirebaseMessaging.DefaultInstance.SendAllAsync(batchMessages); + _logger.LogDebug("Batch {batchNumber}/{totalBatches} sent, messages: {messages}, result: {result}", + i + 1, + batchCount, + JsonConvert.SerializeObject(batchMessages, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }), + JsonConvert.SerializeObject(result, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + } + } + + private Message CreateMessage(UserDeviceIndex device, string icon, string title, string content, + Dictionary data, int badge, DeviceType deviceType) + { + var message = new Message() + { + Notification = MessageHelper.GetNotification(title, content, icon), + Token = device.RegistrationToken, + Data = data + }; + + switch (deviceType) + { + case DeviceType.Android: + message.Android = MessageHelper.GetAndroidConfig(1); // Set badge to 1 for Android + break; + case DeviceType.IOS: + message.Apns = MessageHelper.GetApnsConfig(badge); + break; + case DeviceType.Extension: + message.Webpush = MessageHelper.GetWebPushConfig(badge); + break; + } + + return message; + } + + private void TryHandleExceptionAsync(List userDevices, BatchResponse batchResponse) { if (batchResponse == null || batchResponse.Responses.IsNullOrEmpty()) return; for (var i = 0; i < batchResponse.Responses.Count; i++) { var response = batchResponse.Responses[i]; if (response == null || response.Exception == null) continue; - var user = userDevice[i]; + var user = userDevices[i]; _ = HandleExceptionAsync(response.Exception.Message, user.Id, user.RegistrationToken); } diff --git a/src/MessagePush.Application/MessagePushApplicationModule.cs b/src/MessagePush.Application/MessagePushApplicationModule.cs index 55c5ec0..ffd3b74 100644 --- a/src/MessagePush.Application/MessagePushApplicationModule.cs +++ b/src/MessagePush.Application/MessagePushApplicationModule.cs @@ -1,4 +1,7 @@ -using MessagePush.Grains; +using System.Configuration; +using MessagePush.Grains; +using MessagePush.Options; +using MessagePush.Redis; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Account; using Volo.Abp.AutoMapper; @@ -30,5 +33,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { options.AddMaps(); }); context.Services.AddHttpClient(); + context.Services.AddSingleton(); + + var configuration = context.Services.GetConfiguration(); + Configure(configuration.GetSection("ScheduledTasks")); + Configure(configuration.GetSection("MessagePush")); } } \ No newline at end of file diff --git a/src/MessagePush.Application/Options/MessagePushOptions.cs b/src/MessagePush.Application/Options/MessagePushOptions.cs new file mode 100644 index 0000000..2590992 --- /dev/null +++ b/src/MessagePush.Application/Options/MessagePushOptions.cs @@ -0,0 +1,35 @@ +namespace MessagePush.Options; + +/// +/// Options for message push functionality. +/// +public class MessagePushOptions +{ + // Constants + private const int DefaultExpiredDeviceInfoFromDays = 60; + // FCM actually supports a batch size of 500 messages, but we use 400 to avoid the limit. + private const int DefaultSendAllBatchSize = 400; + + // Fields + private int _expiredDeviceInfoFromDays; + private int _sendAllBatchSize; + + + /// + /// Gets or sets the number of days before a device info is considered expired. + /// If not set, defaults to DefaultExpiredDeviceInfoFromDays. + /// + public int ExpiredDeviceInfoFromDays { + get => _expiredDeviceInfoFromDays > 0 ? _expiredDeviceInfoFromDays : DefaultExpiredDeviceInfoFromDays; + set => _expiredDeviceInfoFromDays = value; + } + + /// + /// Gets or sets the batch size for sending all messages. + /// If not set, defaults to DefaultSendAllBatchSize. + /// + public int SendAllBatchSize { + get => _sendAllBatchSize > 0 ? _sendAllBatchSize : DefaultSendAllBatchSize; + set => _sendAllBatchSize = value; + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/Options/ScheduledTasksOptions.cs b/src/MessagePush.Application/Options/ScheduledTasksOptions.cs new file mode 100644 index 0000000..1a584dd --- /dev/null +++ b/src/MessagePush.Application/Options/ScheduledTasksOptions.cs @@ -0,0 +1,56 @@ +namespace MessagePush.Options; + +/// +/// Options for scheduled tasks functionality. +/// +public class ScheduledTasksOptions +{ + // Constants for default values + private const int DefaultExecutionHour = 8; + private const int DefaultExpiredDeviceInfoFromDays = 60; + private const int DefaultExpiredDeviceInfoLimit = 200; + private const int DefaultDelayFromMilliseconds = 200; + + // Fields to store the configuration values + private int _executionHour; + private int _expiredDeviceInfoFromDays; + private int _expiredDeviceInfoLimit; + private int _delayFromMilliseconds; + + /// + /// Gets or sets the hour of the day when the scheduled tasks should be executed. + /// If not set, defaults to DefaultExecutionHour. + /// + public int ExecutionHour + { + get => _executionHour > 0 ? _executionHour : DefaultExecutionHour; + set => _executionHour = value; + } + + /// + /// Gets or sets the number of days before a device info is considered expired. + /// If not set, defaults to DefaultExpiredDeviceInfoFromDays. + /// + public int ExpiredDeviceInfoFromDays { + get => _expiredDeviceInfoFromDays > 0 ? _expiredDeviceInfoFromDays : DefaultExpiredDeviceInfoFromDays; + set => _expiredDeviceInfoFromDays = value; + } + + /// + /// Gets or sets the limit for the number of expired device info to be processed at once. + /// If not set, defaults to DefaultExpiredDeviceInfoLimit. + /// + public int ExpiredDeviceInfoLimit { + get => _expiredDeviceInfoLimit > 0 ? _expiredDeviceInfoLimit : DefaultExpiredDeviceInfoLimit; + set => _expiredDeviceInfoLimit = value; + } + + /// + /// Gets or sets the delay in milliseconds between batches of processing expired device info. + /// If not set, defaults to DefaultDelayFromMilliseconds. + /// + public int DelayFromMilliseconds { + get => _delayFromMilliseconds > 0 ? _delayFromMilliseconds : DefaultDelayFromMilliseconds; + set => _delayFromMilliseconds = value; + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/Redis/RedisClient.cs b/src/MessagePush.Application/Redis/RedisClient.cs new file mode 100644 index 0000000..b3f40d9 --- /dev/null +++ b/src/MessagePush.Application/Redis/RedisClient.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Configuration; +using StackExchange.Redis; + +namespace MessagePush.Redis; + +public class RedisClient +{ + private readonly ConnectionMultiplexer _connection; + private readonly IDatabase _database; + + public RedisClient(IConfiguration configuration) + { + var redisConfiguration = configuration.GetSection("Redis:Configuration").Value; + _connection = ConnectionMultiplexer.Connect(redisConfiguration); + _database = _connection.GetDatabase(); + } + + public void Set(string key, int value) + { + _database.StringSet(key, value); + } + + public int Get(string key) + { + var value = _database.StringGet(key); + return (int)value; + } + + public int IncrementAndGet(string key) + { + return (int) _database.StringIncrement(key); + } + + public int GetAndIncrement(string key) + { + return (int) _database.StringIncrement(key) - 1; + } + + public bool AddIfNotExists(string key, int value) + { + return _database.StringSet(key, value, when: When.NotExists); + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/ScheduledTasks/Jobs/DeleteExpiredDeviceInfoJob.cs b/src/MessagePush.Application/ScheduledTasks/Jobs/DeleteExpiredDeviceInfoJob.cs new file mode 100644 index 0000000..f718347 --- /dev/null +++ b/src/MessagePush.Application/ScheduledTasks/Jobs/DeleteExpiredDeviceInfoJob.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MessagePush.Commons; +using MessagePush.DeviceInfo.Provider; +using MessagePush.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Volo.Abp.Application.Services; + +namespace MessagePush.ScheduledTasks.Jobs; + +using Quartz; + +public class DeleteExpiredDeviceInfoJob : ApplicationService, IJob +{ + private readonly IUserDeviceProvider _userDeviceProvider; + private readonly ScheduledTasksOptions _scheduledTasks; + + public DeleteExpiredDeviceInfoJob(IUserDeviceProvider userDeviceProvider, IOptionsSnapshot scheduledTasks) + { + _userDeviceProvider = userDeviceProvider; + _scheduledTasks = scheduledTasks.Value; + } + + // This job is designed to run on a single instance (single server, single application). + // If the application is deployed in a cluster in the future, this job may need to be modified to work correctly in a clustered environment. + public async Task Execute(IJobExecutionContext context) + { + Logger.LogInformation("Entering Execute method in DeleteExpiredDeviceInfoJob"); + + var criteria = new ExpiredDeviceCriteria() + { + FromDays = _scheduledTasks.ExpiredDeviceInfoFromDays, + Limit = _scheduledTasks.ExpiredDeviceInfoLimit + }; + + var expiredDeviceInfos = await _userDeviceProvider.GetExpiredDeviceInfos(criteria); + + while (expiredDeviceInfos != null && expiredDeviceInfos.Any()) + { + foreach (var expiredDeviceInfo in expiredDeviceInfos) + { + Logger.LogInformation($"Starting to delete expired device info with ID: {expiredDeviceInfo.Id}, Expired device info: {JsonConvert.SerializeObject(expiredDeviceInfo)}"); + await _userDeviceProvider.DeleteUserDeviceAsync(expiredDeviceInfo.Id); + } + + await Task.Delay(TimeSpan.FromMilliseconds(_scheduledTasks.DelayFromMilliseconds)); + + // Check if there are more devices to delete + if (expiredDeviceInfos.Count == criteria.Limit) + { + // If the previous query returned the maximum number of devices, there might be more devices to delete + expiredDeviceInfos = await _userDeviceProvider.GetExpiredDeviceInfos(criteria); + } + else + { + // If the previous query returned less than the maximum number of devices, there are no more devices to delete + expiredDeviceInfos = null; + } + } + + Logger.LogInformation("Exiting Execute method in DeleteExpiredDeviceInfoJob"); + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/ScheduledTasks/QuartzJobFactory.cs b/src/MessagePush.Application/ScheduledTasks/QuartzJobFactory.cs new file mode 100644 index 0000000..04ee540 --- /dev/null +++ b/src/MessagePush.Application/ScheduledTasks/QuartzJobFactory.cs @@ -0,0 +1,25 @@ +using System; +using Quartz; +using Quartz.Spi; + +namespace MessagePush.ScheduledTasks; + +public class QuartzJobFactory : IJobFactory +{ + private readonly IServiceProvider _serviceProvider; + + public QuartzJobFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) + { + return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob; + } + + public void ReturnJob(IJob job) + { + // Here you can dispose/release the job if needed. + } +} \ No newline at end of file diff --git a/src/MessagePush.Application/ScheduledTasks/QuartzStartup.cs b/src/MessagePush.Application/ScheduledTasks/QuartzStartup.cs new file mode 100644 index 0000000..3c80594 --- /dev/null +++ b/src/MessagePush.Application/ScheduledTasks/QuartzStartup.cs @@ -0,0 +1,63 @@ +using System; +using MessagePush.Options; +using MessagePush.ScheduledTasks.Jobs; +using Microsoft.Extensions.Options; +using Volo.Abp.Application.Services; +using Volo.Abp.Auditing; + +namespace MessagePush.ScheduledTasks; + +using Quartz; +using Quartz.Impl; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +[DisableAuditing] +public class QuartzStartup : ApplicationService, IHostedService +{ + + private readonly ScheduledTasksOptions _scheduledTasks; + private readonly IServiceProvider _serviceProvider; + + public QuartzStartup(IOptionsSnapshot scheduledTasks, IServiceProvider serviceProvider) + { + _scheduledTasks = scheduledTasks.Value; + _serviceProvider = serviceProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + Logger.LogInformation("Quartz service is starting"); + + IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; + scheduler.JobFactory = new QuartzJobFactory(_serviceProvider); // Set the custom JobFactory + scheduler.Start(); + + IJobDetail job = JobBuilder.Create().Build(); + + ITrigger trigger = TriggerBuilder.Create() + .WithDailyTimeIntervalSchedule + (s => + s.WithIntervalInHours(24) + .OnEveryDay() + .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay( + _scheduledTasks.ExecutionHour, 20)) + ) + .Build(); + + scheduler.ScheduleJob(job, trigger); + + Logger.LogInformation("Quartz service has started"); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + Logger.LogInformation("Quartz service is stopping"); + + Logger.LogInformation("Quartz service has stopped"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/MessagePush.DbMigrator/MessagePush.DbMigrator.csproj b/src/MessagePush.DbMigrator/MessagePush.DbMigrator.csproj index 9625ed3..37c94f8 100644 --- a/src/MessagePush.DbMigrator/MessagePush.DbMigrator.csproj +++ b/src/MessagePush.DbMigrator/MessagePush.DbMigrator.csproj @@ -22,6 +22,7 @@ + diff --git a/src/MessagePush.Domain.Shared/MessagePush.Domain.Shared.csproj b/src/MessagePush.Domain.Shared/MessagePush.Domain.Shared.csproj index 8ad8764..ac500ee 100644 --- a/src/MessagePush.Domain.Shared/MessagePush.Domain.Shared.csproj +++ b/src/MessagePush.Domain.Shared/MessagePush.Domain.Shared.csproj @@ -9,6 +9,7 @@ + diff --git a/src/MessagePush.Domain/Entities/Es/UnreadMessageIndex.cs b/src/MessagePush.Domain/Entities/Es/UnreadMessageIndex.cs deleted file mode 100644 index 425180e..0000000 --- a/src/MessagePush.Domain/Entities/Es/UnreadMessageIndex.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using AElf.Indexing.Elasticsearch; -using Nest; - -namespace MessagePush.Entities.Es; - -public class UnreadMessageIndex : MessagePushEsEntity, IIndexBuild -{ - [Keyword] public override string Id { get; set; } - [Keyword] public string UserId { get; set; } - [Keyword] public string AppId { get; set; } - public List UnreadMessageInfos { get; set; } -} - -public class UnreadMessageInfo -{ - [Keyword] public string MessageType { get; set; } - public int UnreadCount { get; set; } -} \ No newline at end of file diff --git a/src/MessagePush.Domain/Entities/Redis/UnreadMessage.cs b/src/MessagePush.Domain/Entities/Redis/UnreadMessage.cs new file mode 100644 index 0000000..f40f315 --- /dev/null +++ b/src/MessagePush.Domain/Entities/Redis/UnreadMessage.cs @@ -0,0 +1,14 @@ +namespace MessagePush.Entities.Redis; + +public class UnreadMessage +{ + public string UserId { get; set; } + public string AppId { get; set; } + public string MessageType { get; set; } + public int UnreadCount { get; set; } + + public string GetKey() + { + return UserId + "-" + AppId + "-" + MessageType; + } +} \ No newline at end of file diff --git a/src/MessagePush.Domain/MessagePush.Domain.csproj b/src/MessagePush.Domain/MessagePush.Domain.csproj index 4258808..75eec80 100644 --- a/src/MessagePush.Domain/MessagePush.Domain.csproj +++ b/src/MessagePush.Domain/MessagePush.Domain.csproj @@ -17,6 +17,7 @@ + diff --git a/src/MessagePush.EntityEventHandler.Core/MessagePush.EntityEventHandler.Core.csproj b/src/MessagePush.EntityEventHandler.Core/MessagePush.EntityEventHandler.Core.csproj index 3c51bea..0519b2b 100644 --- a/src/MessagePush.EntityEventHandler.Core/MessagePush.EntityEventHandler.Core.csproj +++ b/src/MessagePush.EntityEventHandler.Core/MessagePush.EntityEventHandler.Core.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/src/MessagePush.EntityEventHandler/MessagePush.EntityEventHandler.csproj b/src/MessagePush.EntityEventHandler/MessagePush.EntityEventHandler.csproj index 62ad661..d2232b2 100644 --- a/src/MessagePush.EntityEventHandler/MessagePush.EntityEventHandler.csproj +++ b/src/MessagePush.EntityEventHandler/MessagePush.EntityEventHandler.csproj @@ -8,6 +8,7 @@ + diff --git a/src/MessagePush.Grains/MessagePush.Grains.csproj b/src/MessagePush.Grains/MessagePush.Grains.csproj index 6df5c3e..84371f4 100644 --- a/src/MessagePush.Grains/MessagePush.Grains.csproj +++ b/src/MessagePush.Grains/MessagePush.Grains.csproj @@ -20,6 +20,7 @@ + diff --git a/src/MessagePush.HttpApi.Client/MessagePush.HttpApi.Client.csproj b/src/MessagePush.HttpApi.Client/MessagePush.HttpApi.Client.csproj index f70730f..ed8f66a 100644 --- a/src/MessagePush.HttpApi.Client/MessagePush.HttpApi.Client.csproj +++ b/src/MessagePush.HttpApi.Client/MessagePush.HttpApi.Client.csproj @@ -12,6 +12,7 @@ + diff --git a/src/MessagePush.HttpApi.Host/MessagePush.HttpApi.Host.csproj b/src/MessagePush.HttpApi.Host/MessagePush.HttpApi.Host.csproj index ab53c0d..4086d0c 100644 --- a/src/MessagePush.HttpApi.Host/MessagePush.HttpApi.Host.csproj +++ b/src/MessagePush.HttpApi.Host/MessagePush.HttpApi.Host.csproj @@ -11,6 +11,7 @@ + diff --git a/src/MessagePush.HttpApi.Host/Program.cs b/src/MessagePush.HttpApi.Host/Program.cs index 892e38c..fdd8647 100644 --- a/src/MessagePush.HttpApi.Host/Program.cs +++ b/src/MessagePush.HttpApi.Host/Program.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using MessagePush.ScheduledTasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -38,7 +39,10 @@ public async static Task Main(string[] args) builder.Host.AddAppSettingsSecretsJson() .UseAutofac() .UseSerilog(); + builder.Services.AddSignalR(); + builder.Services.AddHostedService(); + await builder.AddApplicationAsync(); var app = builder.Build(); await app.InitializeApplicationAsync(); diff --git a/src/MessagePush.HttpApi.Host/appsettings.json b/src/MessagePush.HttpApi.Host/appsettings.json index 42ff35f..de06ebf 100755 --- a/src/MessagePush.HttpApi.Host/appsettings.json +++ b/src/MessagePush.HttpApi.Host/appsettings.json @@ -69,5 +69,15 @@ }, "Settings": { "Abp.Account.IsSelfRegistrationEnabled": false + }, + "ScheduledTasks": { + "ExecutionHour": 8, + "ExpiredDeviceInfoFromDays": 60, + "ExpiredDeviceInfoLimit": 200, + "DelayFromMilliseconds": 200 + }, + "MessagePush": { + "ExpiredDeviceInfoFromDays": 60, + "SendAllBatchSize": 400 } } diff --git a/src/MessagePush.HttpApi/MessagePush.HttpApi.csproj b/src/MessagePush.HttpApi/MessagePush.HttpApi.csproj index 66ec6dc..1e6d9b0 100644 --- a/src/MessagePush.HttpApi/MessagePush.HttpApi.csproj +++ b/src/MessagePush.HttpApi/MessagePush.HttpApi.csproj @@ -13,6 +13,7 @@ + diff --git a/src/MessagePush.MongoDB/MessagePush.MongoDB.csproj b/src/MessagePush.MongoDB/MessagePush.MongoDB.csproj index bcb029b..aa4fb58 100644 --- a/src/MessagePush.MongoDB/MessagePush.MongoDB.csproj +++ b/src/MessagePush.MongoDB/MessagePush.MongoDB.csproj @@ -9,6 +9,7 @@ + diff --git a/src/MessagePush.Silo/MessagePush.Silo.csproj b/src/MessagePush.Silo/MessagePush.Silo.csproj index 8d2c1c0..b9cb83b 100644 --- a/src/MessagePush.Silo/MessagePush.Silo.csproj +++ b/src/MessagePush.Silo/MessagePush.Silo.csproj @@ -14,6 +14,7 @@ + diff --git a/test/MessagePush.Application.Tests/MessagePush.Application.Tests.csproj b/test/MessagePush.Application.Tests/MessagePush.Application.Tests.csproj index 591657f..d4b534a 100644 --- a/test/MessagePush.Application.Tests/MessagePush.Application.Tests.csproj +++ b/test/MessagePush.Application.Tests/MessagePush.Application.Tests.csproj @@ -22,6 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/MessagePush.Domain.Tests/MessagePush.Domain.Tests.csproj b/test/MessagePush.Domain.Tests/MessagePush.Domain.Tests.csproj index 34f9352..7834ab3 100644 --- a/test/MessagePush.Domain.Tests/MessagePush.Domain.Tests.csproj +++ b/test/MessagePush.Domain.Tests/MessagePush.Domain.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/test/MessagePush.Grain.Tests/MessagePush.Grain.Tests.csproj b/test/MessagePush.Grain.Tests/MessagePush.Grain.Tests.csproj index 387e6a0..ebedb93 100644 --- a/test/MessagePush.Grain.Tests/MessagePush.Grain.Tests.csproj +++ b/test/MessagePush.Grain.Tests/MessagePush.Grain.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/test/MessagePush.MongoDB.Tests/MessagePush.MongoDB.Tests.csproj b/test/MessagePush.MongoDB.Tests/MessagePush.MongoDB.Tests.csproj index dd93640..9d61b10 100644 --- a/test/MessagePush.MongoDB.Tests/MessagePush.MongoDB.Tests.csproj +++ b/test/MessagePush.MongoDB.Tests/MessagePush.MongoDB.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/test/MessagePush.Orleans.TestBase/MessagePush.Orleans.TestBase.csproj b/test/MessagePush.Orleans.TestBase/MessagePush.Orleans.TestBase.csproj index bd484b4..4ad4951 100644 --- a/test/MessagePush.Orleans.TestBase/MessagePush.Orleans.TestBase.csproj +++ b/test/MessagePush.Orleans.TestBase/MessagePush.Orleans.TestBase.csproj @@ -11,6 +11,7 @@ + diff --git a/test/MessagePush.TestBase/MessagePush.TestBase.csproj b/test/MessagePush.TestBase/MessagePush.TestBase.csproj index 12f47f2..e36c33a 100644 --- a/test/MessagePush.TestBase/MessagePush.TestBase.csproj +++ b/test/MessagePush.TestBase/MessagePush.TestBase.csproj @@ -8,6 +8,7 @@ +