From 0cb6f0a9790a3bb129efeb4f600df79c3ce249da Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Thu, 12 Jan 2023 09:51:10 +0800 Subject: [PATCH] refactoring DynamicNotificationDefinitionStore --- ...onsCommonNotificationDefinitionProvider.cs | 4 +- .../INotificationDefinitionContext.cs | 1 + .../Notifications/NotificationDefinition.cs | 5 + .../NotificationDefinitionContext.cs | 3 +- .../NotificationGroupDefinition.cs | 12 +- .../AbpNotificationsManagementOptions.cs | 24 +- .../DynamicNotificationDefinitionCache.cs | 326 ------------------ ...amicNotificationDefinitionInMemoryCache.cs | 113 ++++++ .../DynamicNotificationDefinitionStore.cs | 156 ++++++++- .../IDynamicNotificationDefinitionCache.cs | 13 - ...DynamicNotificationDefinitionStoreCache.cs | 25 ++ 11 files changed, 320 insertions(+), 362 deletions(-) delete mode 100644 aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionCache.cs create mode 100644 aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs delete mode 100644 aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionCache.cs create mode 100644 aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Common/LINGYUN/Abp/Notifications/NotificationsCommonNotificationDefinitionProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Common/LINGYUN/Abp/Notifications/NotificationsCommonNotificationDefinitionProvider.cs index 91d5856ec..d4a206366 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Common/LINGYUN/Abp/Notifications/NotificationsCommonNotificationDefinitionProvider.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Common/LINGYUN/Abp/Notifications/NotificationsCommonNotificationDefinitionProvider.cs @@ -12,7 +12,7 @@ public class NotificationsCommonNotificationDefinitionProvider : NotificationDef var commonGroup = context.AddGroup( NotificationsCommonNotificationNames.GroupName, L("Notifications:Primitives"), - false); + allowSubscriptionToClients: false); commonGroup.AddNotification( name: NotificationsCommonNotificationNames.ExceptionHandling, @@ -33,7 +33,7 @@ public class NotificationsCommonNotificationDefinitionProvider : NotificationDef var tenantsGroup = context.AddGroup( TenantNotificationNames.GroupName, L("Notifications:MultiTenancy"), - false); + allowSubscriptionToClients: false); tenantsGroup.AddNotification( TenantNotificationNames.NewTenantRegistered, diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionContext.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionContext.cs index e6839d1df..a225abd3f 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionContext.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/INotificationDefinitionContext.cs @@ -8,6 +8,7 @@ namespace LINGYUN.Abp.Notifications NotificationGroupDefinition AddGroup( [NotNull] string name, ILocalizableString displayName = null, + ILocalizableString description = null, bool allowSubscriptionToClients = true); NotificationGroupDefinition GetGroupOrNull(string name); diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs index 9e8ee042d..25ede1f4b 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinition.cs @@ -64,6 +64,11 @@ namespace LINGYUN.Abp.Notifications [NotNull] public Dictionary Properties { get; } + public object this[string name] { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + public NotificationDefinition( string name, ILocalizableString displayName = null, diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionContext.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionContext.cs index b0da3077c..52dc2dc8e 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionContext.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionContext.cs @@ -17,6 +17,7 @@ namespace LINGYUN.Abp.Notifications public NotificationGroupDefinition AddGroup( [NotNull] string name, ILocalizableString displayName = null, + ILocalizableString description = null, bool allowSubscriptionToClients = true) { Check.NotNull(name, nameof(name)); @@ -26,7 +27,7 @@ namespace LINGYUN.Abp.Notifications throw new AbpException($"There is already an existing notification group with name: {name}"); } - return Groups[name] = new NotificationGroupDefinition(name, displayName, allowSubscriptionToClients); + return Groups[name] = new NotificationGroupDefinition(name, displayName, description, allowSubscriptionToClients); } public NotificationGroupDefinition GetGroupOrNull(string name) diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs index 031557e1a..d1dce9c1c 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationGroupDefinition.cs @@ -32,23 +32,31 @@ namespace LINGYUN.Abp.Notifications public IReadOnlyList Notifications => _notifications.ToImmutableList(); private readonly List _notifications; - public Dictionary Properties { get; } + public Dictionary Properties { get; } + + public object this[string name] { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } public static NotificationGroupDefinition Create( string name, ILocalizableString displayName = null, + ILocalizableString description = null, bool allowSubscriptionToClients = false) { - return new NotificationGroupDefinition(name, displayName, allowSubscriptionToClients); + return new NotificationGroupDefinition(name, displayName, description, allowSubscriptionToClients); } protected internal NotificationGroupDefinition( string name, ILocalizableString displayName = null, + ILocalizableString description = null, bool allowSubscriptionToClients = false) { Name = name; DisplayName = displayName ?? new FixedLocalizableString(Name); + Description = description ?? DisplayName; AllowSubscriptionToClients = allowSubscriptionToClients; _notifications = new List(); diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/AbpNotificationsManagementOptions.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/AbpNotificationsManagementOptions.cs index bc2c9de77..70ddb801d 100644 --- a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/AbpNotificationsManagementOptions.cs +++ b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/AbpNotificationsManagementOptions.cs @@ -1,12 +1,34 @@ -namespace LINGYUN.Abp.Notifications; +using System; + +namespace LINGYUN.Abp.Notifications; public class AbpNotificationsManagementOptions { public bool SaveStaticNotificationsToDatabase { get; set; } public bool IsDynamicNotificationsStoreEnabled { get; set; } + /// + /// 缓存刷新时间 + /// default: 30 seconds + /// + public TimeSpan NotificationsCacheRefreshInterval { get; set; } + /// + /// 申请时间戳超时时间 + /// default: 2 minutes + /// + public TimeSpan NotificationsCacheStampTimeOut { get; set; } + /// + /// 时间戳过期时间 + /// default: 30 days + /// + public TimeSpan NotificationsCacheStampExpiration { get; set; } public AbpNotificationsManagementOptions() { SaveStaticNotificationsToDatabase = true; IsDynamicNotificationsStoreEnabled = true; + + NotificationsCacheRefreshInterval = TimeSpan.FromSeconds(30); + NotificationsCacheStampTimeOut = TimeSpan.FromMinutes(2); + // 30天过期重新刷新缓存 + NotificationsCacheStampExpiration = TimeSpan.FromDays(30); } } diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionCache.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionCache.cs deleted file mode 100644 index db331e254..000000000 --- a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionCache.cs +++ /dev/null @@ -1,326 +0,0 @@ -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Localization; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; -using Volo.Abp.Threading; - -namespace LINGYUN.Abp.Notifications; - -public class DynamicNotificationDefinitionCache : IDynamicNotificationDefinitionCache, ISingletonDependency -{ - private readonly static ConcurrentDictionary _dynamicNotificationGroupL1Cache; - private readonly static ConcurrentDictionary _dynamicNotificationsL1Cache; - - private readonly static SemaphoreSlim _l2CacheSyncLock; - protected IMemoryCache DynamicNotificationL2Cache { get; } - - protected IDistributedCache DynamicNotificationGroupL3Cache { get; } - protected IDistributedCache DynamicNotificationsL3Cache { get; } - - - protected INotificationDefinitionGroupRecordRepository NotificationDefinitionGroupRecordRepository { get; } - protected INotificationDefinitionRecordRepository NotificationDefinitionRecordRepository { get; } - protected ILocalizableStringSerializer LocalizableStringSerializer { get; } - protected IStringLocalizerFactory StringLocalizerFactory { get; } - - static DynamicNotificationDefinitionCache() - { - _l2CacheSyncLock = new SemaphoreSlim(1, 1); - _dynamicNotificationsL1Cache = new ConcurrentDictionary(); - _dynamicNotificationGroupL1Cache = new ConcurrentDictionary(); - } - - public DynamicNotificationDefinitionCache( - IMemoryCache dynamicNotificationL2Cache, - IDistributedCache dynamicNotificationGroupL3Cache, - IDistributedCache dynamicNotificationsL3Cache, - INotificationDefinitionGroupRecordRepository notificationDefinitionGroupRecordRepository, - INotificationDefinitionRecordRepository notificationDefinitionRecordRepository, - ILocalizableStringSerializer localizableStringSerializer, - IStringLocalizerFactory stringLocalizerFactory) - { - DynamicNotificationL2Cache = dynamicNotificationL2Cache; - DynamicNotificationGroupL3Cache = dynamicNotificationGroupL3Cache; - DynamicNotificationsL3Cache = dynamicNotificationsL3Cache; - NotificationDefinitionGroupRecordRepository = notificationDefinitionGroupRecordRepository; - NotificationDefinitionRecordRepository = notificationDefinitionRecordRepository; - LocalizableStringSerializer = localizableStringSerializer; - StringLocalizerFactory = stringLocalizerFactory; - } - - public async virtual Task> GetGroupsAsync() - { - var cacheKey = NotificationDefinitionGroupsCacheItem.CalculateCacheKey(CultureInfo.CurrentCulture.Name); - - if (!_dynamicNotificationGroupL1Cache.Any()) - { - var l2GroupCache = await GetGroupsFormL2CacheAsync(cacheKey); - var notifications = await GetNotificationsFormL2CacheAsync( - NotificationDefinitionsCacheItem.CalculateCacheKey(CultureInfo.CurrentCulture.Name)); - - foreach (var group in l2GroupCache.Groups) - { - var groupGroup = NotificationGroupDefinition - .Create( - group.Name, - LocalizableStringSerializer.Deserialize(group.DisplayName), - group.AllowSubscriptionToClients); - - var notificationsInThisGroup = notifications.Notifications - .Where(p => p.GroupName == groupGroup.Name); - - foreach (var notification in notificationsInThisGroup) - { - var notificationDefine = groupGroup.AddNotification( - notification.Name, - LocalizableStringSerializer.Deserialize(notification.DisplayName), - LocalizableStringSerializer.Deserialize(notification.Description), - notification.NotificationType, - notification.Lifetime, - notification.ContentType, - notification.AllowSubscriptionToClients); - - notificationDefine.Properties.AddIfNotContains(notification.Properties); - } - - _dynamicNotificationGroupL1Cache.TryAdd(group.Name, groupGroup); - } - - //NotificationGroupDefinition - // .Create(g.Name, new FixedLocalizableString(g.DisplayName), g.AllowSubscriptionToClients) - } - - return _dynamicNotificationGroupL1Cache.Values.ToImmutableList(); - } - - public async virtual Task> GetNotificationsAsync() - { - var cacheKey = NotificationDefinitionsCacheItem.CalculateCacheKey(CultureInfo.CurrentCulture.Name); - - if (!_dynamicNotificationsL1Cache.Any()) - { - var l2cache = await GetNotificationsFormL2CacheAsync(cacheKey); - - foreach (var notification in l2cache.Notifications) - { - var notificationDefinition = new NotificationDefinition( - notification.Name, - LocalizableStringSerializer.Deserialize(notification.DisplayName), - LocalizableStringSerializer.Deserialize(notification.Description), - notification.NotificationType, - notification.Lifetime, - notification.ContentType, - notification.AllowSubscriptionToClients); - - notificationDefinition.WithProviders(notification.Providers.ToArray()); - notificationDefinition.Properties.AddIfNotContains(notification.Properties); - - _dynamicNotificationsL1Cache.TryAdd(notification.Name, notificationDefinition); - } - } - - return _dynamicNotificationsL1Cache.Values.ToImmutableList(); - } - - public async virtual Task GetOrNullAsync(string name) - { - if (_dynamicNotificationsL1Cache.Any()) - { - return _dynamicNotificationsL1Cache.GetOrDefault(name); - } - var notifications = await GetNotificationsAsync(); - - return notifications.FirstOrDefault(n => n.Name.Equals(name)); - } - - protected async virtual Task GetGroupsFormL2CacheAsync(string cacheKey) - { - var cacheItem = DynamicNotificationL2Cache.Get(cacheKey); - - if (cacheItem == null) - { - using (await _l2CacheSyncLock.LockAsync()) - { - var l2cache = await GetGroupsFormL3CacheAsync(cacheKey); - - var options = new MemoryCacheEntryOptions(); - options.SetAbsoluteExpiration(TimeSpan.FromMinutes(5)); - options.SetPriority(CacheItemPriority.High); - options.RegisterPostEvictionCallback((key, value, reason, state) => - { - _dynamicNotificationGroupL1Cache.Clear(); - }); - - DynamicNotificationL2Cache.Set(cacheKey, l2cache, options); - - return l2cache; - } - } - - return cacheItem; - } - - protected async virtual Task GetGroupsFormL3CacheAsync(string cacheKey) - { - var cacheItem = await DynamicNotificationGroupL3Cache.GetAsync(cacheKey); - - if (cacheItem == null) - { - var records = await GetGroupsFormRepositoryAsync(); - var recordCaches = new List(); - - foreach (var record in records) - { - var displayName = record.DisplayName; - if (!displayName.IsNullOrWhiteSpace()) - { - displayName = await LocalizableStringSerializer - .Deserialize(displayName) - .LocalizeAsync(StringLocalizerFactory); - } - - var description = record.Description; - if (!description.IsNullOrWhiteSpace()) - { - description = await LocalizableStringSerializer - .Deserialize(description) - .LocalizeAsync(StringLocalizerFactory); - } - - recordCaches.Add(new NotificationDefinitionGroupCacheItem( - record.Name, - displayName, - description, - record.AllowSubscriptionToClients)); - } - - cacheItem = new NotificationDefinitionGroupsCacheItem(recordCaches.ToArray()); - - await DynamicNotificationGroupL3Cache.SetAsync( - cacheKey, - cacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2), - SlidingExpiration = TimeSpan.FromHours(1), - }); - } - - return cacheItem; - } - - protected async virtual Task> GetGroupsFormRepositoryAsync() - { - var records = await NotificationDefinitionGroupRecordRepository.GetListAsync(); - - return records; - } - - - protected async virtual Task GetNotificationsFormL2CacheAsync(string cacheKey) - { - var cacheItem = DynamicNotificationL2Cache.Get(cacheKey); - - if (cacheItem == null) - { - using (await _l2CacheSyncLock.LockAsync()) - { - var l2cache = await GetNotificationsFormL3CacheAsync(cacheKey); - - var options = new MemoryCacheEntryOptions(); - options.SetAbsoluteExpiration(TimeSpan.FromMinutes(5)); - options.SetPriority(CacheItemPriority.High); - options.RegisterPostEvictionCallback((key, value, reason, state) => - { - _dynamicNotificationsL1Cache.Clear(); - }); - - DynamicNotificationL2Cache.Set(cacheKey, l2cache, options); - - return l2cache; - } - } - - return cacheItem; - } - - protected async virtual Task GetNotificationsFormL3CacheAsync(string cacheKey) - { - var cacheItem = await DynamicNotificationsL3Cache.GetAsync(cacheKey); - - if (cacheItem == null) - { - var records = await GetNotificationsFormRepositoryAsync(); - var recordCaches = new List(); - - foreach (var record in records) - { - var displayName = record.DisplayName; - if (!displayName.IsNullOrWhiteSpace()) - { - displayName = await LocalizableStringSerializer - .Deserialize(displayName) - .LocalizeAsync(StringLocalizerFactory); - } - - var description = record.Description; - if (!description.IsNullOrWhiteSpace()) - { - description = await LocalizableStringSerializer - .Deserialize(description) - .LocalizeAsync(StringLocalizerFactory); - } - - var providers = new List(); - if (!record.Providers.IsNullOrWhiteSpace()) - { - providers = record.Providers.Split(';').ToList(); - } - - var recordCache = new NotificationDefinitionCacheItem( - record.Name, - record.GroupName, - displayName, - description, - record.NotificationLifetime, - record.NotificationType, - record.ContentType, - providers, - record.AllowSubscriptionToClients); - recordCache.Properties.AddIfNotContains(record.ExtraProperties); - - recordCaches.Add(recordCache); - } - - cacheItem = new NotificationDefinitionsCacheItem(recordCaches.ToArray()); - - await DynamicNotificationsL3Cache.SetAsync( - cacheKey, - cacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2), - SlidingExpiration = TimeSpan.FromHours(1), - }); - } - - return cacheItem; - } - - protected async virtual Task> GetNotificationsFormRepositoryAsync() - { - var records = await NotificationDefinitionRecordRepository.GetListAsync(); - - return records; - } -} diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs new file mode 100644 index 000000000..404a6c3c2 --- /dev/null +++ b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionInMemoryCache.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Notifications; + +[ExposeServices( + typeof(IDynamicNotificationDefinitionStoreCache), + typeof(DynamicNotificationDefinitionInMemoryCache))] +public class DynamicNotificationDefinitionInMemoryCache : IDynamicNotificationDefinitionStoreCache, ISingletonDependency +{ + public string CacheStamp { get; set; } + + protected IDictionary NotificationGroupDefinitions { get; } + protected IDictionary NotificationDefinitions { get; } + protected ILocalizableStringSerializer LocalizableStringSerializer { get; } + + public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); + + public DateTime? LastCheckTime { get; set; } + + public DynamicNotificationDefinitionInMemoryCache( + ILocalizableStringSerializer localizableStringSerializer) + { + LocalizableStringSerializer = localizableStringSerializer; + + NotificationGroupDefinitions = new Dictionary(); + NotificationDefinitions = new Dictionary(); + } + + public Task FillAsync( + List notificationGroupRecords, + List notificationRecords) + { + NotificationGroupDefinitions.Clear(); + NotificationDefinitions.Clear(); + + var context = new NotificationDefinitionContext(); + + foreach (var notificationGroupRecord in notificationGroupRecords) + { + var notificationGroup = context.AddGroup( + notificationGroupRecord.Name, + LocalizableStringSerializer.Deserialize(notificationGroupRecord.DisplayName), + LocalizableStringSerializer.Deserialize(notificationGroupRecord.Description), + notificationGroupRecord.AllowSubscriptionToClients + ); + + NotificationGroupDefinitions[notificationGroup.Name] = notificationGroup; + + foreach (var property in notificationGroupRecord.ExtraProperties) + { + notificationGroup[property.Key] = property.Value; + } + + var notificationRecordsInThisGroup = notificationRecords + .Where(p => p.GroupName == notificationGroup.Name); + + foreach (var notificationRecord in notificationRecordsInThisGroup) + { + AddNotification(notificationGroup, notificationRecord); + } + } + + return Task.CompletedTask; + } + + public NotificationDefinition GetNotificationOrNull(string name) + { + return NotificationDefinitions.GetOrDefault(name); + } + + public IReadOnlyList GetNotifications() + { + return NotificationDefinitions.Values.ToList(); + } + + public IReadOnlyList GetGroups() + { + return NotificationGroupDefinitions.Values.ToList(); + } + + private void AddNotification( + NotificationGroupDefinition notificationGroup, + NotificationDefinitionRecord notificationRecord) + { + var notification = notificationGroup.AddNotification( + notificationRecord.Name, + LocalizableStringSerializer.Deserialize(notificationRecord.DisplayName), + LocalizableStringSerializer.Deserialize(notificationRecord.Description), + notificationRecord.NotificationType, + notificationRecord.NotificationLifetime, + notificationRecord.ContentType, + notificationRecord.AllowSubscriptionToClients + ); + + NotificationDefinitions[notification.Name] = notification; + + if (!notificationRecord.Providers.IsNullOrWhiteSpace()) + { + notification.Providers.AddRange(notificationRecord.Providers.Split(',', ';')); + } + + foreach (var property in notificationRecord.ExtraProperties) + { + notification[property.Key] = property.Value; + } + } +} diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs index a270ab465..b8675d4aa 100644 --- a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs +++ b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/DynamicNotificationDefinitionStore.cs @@ -1,49 +1,171 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Threading; namespace LINGYUN.Abp.Notifications; [Dependency(ReplaceServices = true)] public class DynamicNotificationDefinitionStore : IDynamicNotificationDefinitionStore, ITransientDependency { - private readonly AbpNotificationsManagementOptions _notificationsManagementOptions; - private readonly IDynamicNotificationDefinitionCache _dynamicNotificationDefinitionCache; + protected INotificationDefinitionGroupRecordRepository NotificationGroupRepository { get; } + protected INotificationDefinitionRecordRepository NotificationRepository { get; } + protected INotificationDefinitionSerializer NotificationDefinitionSerializer { get; } + protected IDynamicNotificationDefinitionStoreCache StoreCache { get; } + protected IDistributedCache DistributedCache { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected AbpNotificationsManagementOptions NotificationManagementOptions { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } public DynamicNotificationDefinitionStore( - IDynamicNotificationDefinitionCache dynamicNotificationDefinitionCache, - IOptions notificationsManagementOptions) + INotificationDefinitionGroupRecordRepository notificationGroupRepository, + INotificationDefinitionRecordRepository notificationRepository, + INotificationDefinitionSerializer notificationDefinitionSerializer, + IDynamicNotificationDefinitionStoreCache storeCache, + IDistributedCache distributedCache, + IOptions cacheOptions, + IOptions notificationManagementOptions, + IAbpDistributedLock distributedLock) { - _dynamicNotificationDefinitionCache = dynamicNotificationDefinitionCache; - _notificationsManagementOptions = notificationsManagementOptions.Value; + NotificationGroupRepository = notificationGroupRepository; + NotificationRepository = notificationRepository; + NotificationDefinitionSerializer = notificationDefinitionSerializer; + StoreCache = storeCache; + DistributedCache = distributedCache; + DistributedLock = distributedLock; + NotificationManagementOptions = notificationManagementOptions.Value; + CacheOptions = cacheOptions.Value; } - public async virtual Task> GetGroupsAsync() + public async virtual Task GetOrNullAsync(string name) { - if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) + if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) { - return Array.Empty(); + return null; + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetNotificationOrNull(name); } - return await _dynamicNotificationDefinitionCache.GetGroupsAsync(); } public async virtual Task> GetNotificationsAsync() { - if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) + if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) { return Array.Empty(); } - return await _dynamicNotificationDefinitionCache.GetNotificationsAsync(); + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetNotifications().ToImmutableList(); + } } - public async virtual Task GetOrNullAsync(string name) + public async virtual Task> GetGroupsAsync() { - if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) + if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) { - return null; + return Array.Empty(); + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetGroups().ToImmutableList(); + } + } + + protected async virtual Task EnsureCacheIsUptoDateAsync() + { + if (StoreCache.LastCheckTime.HasValue && + DateTime.Now.Subtract(StoreCache.LastCheckTime.Value) < NotificationManagementOptions.NotificationsCacheRefreshInterval) + { + /* We get the latest Notification with a small delay for optimization */ + return; + } + + var stampInDistributedCache = await GetOrSetStampInDistributedCache(); + + if (stampInDistributedCache == StoreCache.CacheStamp) + { + StoreCache.LastCheckTime = DateTime.Now; + return; } - return await _dynamicNotificationDefinitionCache.GetOrNullAsync(name); + + await UpdateInMemoryStoreCache(); + + StoreCache.CacheStamp = stampInDistributedCache; + StoreCache.LastCheckTime = DateTime.Now; + } + + protected async virtual Task UpdateInMemoryStoreCache() + { + var NotificationGroupRecords = await NotificationGroupRepository.GetListAsync(); + var NotificationRecords = await NotificationRepository.GetListAsync(); + + await StoreCache.FillAsync(NotificationGroupRecords, NotificationRecords); + } + + protected async virtual Task GetOrSetStampInDistributedCache() + { + var cacheKey = GetCommonStampCacheKey(); + + var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + await using (var commonLockHandle = await DistributedLock + .TryAcquireAsync(GetCommonDistributedLockKey(), NotificationManagementOptions.NotificationsCacheStampTimeOut)) + { + if (commonLockHandle == null) + { + /* This request will fail */ + throw new AbpException( + "Could not acquire distributed lock for Notification definition common stamp check!" + ); + } + + stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + stampInDistributedCache = Guid.NewGuid().ToString(); + + await DistributedCache.SetStringAsync( + cacheKey, + stampInDistributedCache, + new DistributedCacheEntryOptions + { + SlidingExpiration = NotificationManagementOptions.NotificationsCacheStampExpiration + } + ); + } + + return stampInDistributedCache; + } + + protected virtual string GetCommonStampCacheKey() + { + return $"{CacheOptions.KeyPrefix}_AbpInMemoryNotificationCacheStamp"; + } + + protected virtual string GetCommonDistributedLockKey() + { + return $"{CacheOptions.KeyPrefix}_Common_AbpNotificationUpdateLock"; } } diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionCache.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionCache.cs deleted file mode 100644 index fcebc3e87..000000000 --- a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionCache.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Notifications; - -public interface IDynamicNotificationDefinitionCache -{ - Task GetOrNullAsync(string name); - - Task> GetNotificationsAsync(); - - Task> GetGroupsAsync(); -} diff --git a/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs new file mode 100644 index 000000000..adf8d41c7 --- /dev/null +++ b/aspnet-core/modules/notifications/LINGYUN.Abp.Notifications.Domain/LINGYUN/Abp/Notifications/IDynamicNotificationDefinitionStoreCache.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications; + +public interface IDynamicNotificationDefinitionStoreCache +{ + string CacheStamp { get; set; } + + SemaphoreSlim SyncSemaphore { get; } + + DateTime? LastCheckTime { get; set; } + + Task FillAsync( + List webhookGroupRecords, + List webhookRecords); + + NotificationDefinition GetNotificationOrNull(string name); + + IReadOnlyList GetNotifications(); + + IReadOnlyList GetGroups(); +}