11 changed files with 320 additions and 362 deletions
@ -1,12 +1,34 @@ |
|||||
namespace LINGYUN.Abp.Notifications; |
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications; |
||||
|
|
||||
public class AbpNotificationsManagementOptions |
public class AbpNotificationsManagementOptions |
||||
{ |
{ |
||||
public bool SaveStaticNotificationsToDatabase { get; set; } |
public bool SaveStaticNotificationsToDatabase { get; set; } |
||||
public bool IsDynamicNotificationsStoreEnabled { get; set; } |
public bool IsDynamicNotificationsStoreEnabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 缓存刷新时间
|
||||
|
/// default: 30 seconds
|
||||
|
/// </summary>
|
||||
|
public TimeSpan NotificationsCacheRefreshInterval { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 申请时间戳超时时间
|
||||
|
/// default: 2 minutes
|
||||
|
/// </summary>
|
||||
|
public TimeSpan NotificationsCacheStampTimeOut { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 时间戳过期时间
|
||||
|
/// default: 30 days
|
||||
|
/// </summary>
|
||||
|
public TimeSpan NotificationsCacheStampExpiration { get; set; } |
||||
public AbpNotificationsManagementOptions() |
public AbpNotificationsManagementOptions() |
||||
{ |
{ |
||||
SaveStaticNotificationsToDatabase = true; |
SaveStaticNotificationsToDatabase = true; |
||||
IsDynamicNotificationsStoreEnabled = true; |
IsDynamicNotificationsStoreEnabled = true; |
||||
|
|
||||
|
NotificationsCacheRefreshInterval = TimeSpan.FromSeconds(30); |
||||
|
NotificationsCacheStampTimeOut = TimeSpan.FromMinutes(2); |
||||
|
// 30天过期重新刷新缓存
|
||||
|
NotificationsCacheStampExpiration = TimeSpan.FromDays(30); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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<string, NotificationGroupDefinition> _dynamicNotificationGroupL1Cache; |
|
||||
private readonly static ConcurrentDictionary<string, NotificationDefinition> _dynamicNotificationsL1Cache; |
|
||||
|
|
||||
private readonly static SemaphoreSlim _l2CacheSyncLock; |
|
||||
protected IMemoryCache DynamicNotificationL2Cache { get; } |
|
||||
|
|
||||
protected IDistributedCache<NotificationDefinitionGroupsCacheItem> DynamicNotificationGroupL3Cache { get; } |
|
||||
protected IDistributedCache<NotificationDefinitionsCacheItem> 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<string, NotificationDefinition>(); |
|
||||
_dynamicNotificationGroupL1Cache = new ConcurrentDictionary<string, NotificationGroupDefinition>(); |
|
||||
} |
|
||||
|
|
||||
public DynamicNotificationDefinitionCache( |
|
||||
IMemoryCache dynamicNotificationL2Cache, |
|
||||
IDistributedCache<NotificationDefinitionGroupsCacheItem> dynamicNotificationGroupL3Cache, |
|
||||
IDistributedCache<NotificationDefinitionsCacheItem> 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<IReadOnlyList<NotificationGroupDefinition>> 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<IReadOnlyList<NotificationDefinition>> 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<NotificationDefinition> 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<NotificationDefinitionGroupsCacheItem> GetGroupsFormL2CacheAsync(string cacheKey) |
|
||||
{ |
|
||||
var cacheItem = DynamicNotificationL2Cache.Get<NotificationDefinitionGroupsCacheItem>(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<NotificationDefinitionGroupsCacheItem> GetGroupsFormL3CacheAsync(string cacheKey) |
|
||||
{ |
|
||||
var cacheItem = await DynamicNotificationGroupL3Cache.GetAsync(cacheKey); |
|
||||
|
|
||||
if (cacheItem == null) |
|
||||
{ |
|
||||
var records = await GetGroupsFormRepositoryAsync(); |
|
||||
var recordCaches = new List<NotificationDefinitionGroupCacheItem>(); |
|
||||
|
|
||||
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<List<NotificationDefinitionGroupRecord>> GetGroupsFormRepositoryAsync() |
|
||||
{ |
|
||||
var records = await NotificationDefinitionGroupRecordRepository.GetListAsync(); |
|
||||
|
|
||||
return records; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
protected async virtual Task<NotificationDefinitionsCacheItem> GetNotificationsFormL2CacheAsync(string cacheKey) |
|
||||
{ |
|
||||
var cacheItem = DynamicNotificationL2Cache.Get<NotificationDefinitionsCacheItem>(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<NotificationDefinitionsCacheItem> GetNotificationsFormL3CacheAsync(string cacheKey) |
|
||||
{ |
|
||||
var cacheItem = await DynamicNotificationsL3Cache.GetAsync(cacheKey); |
|
||||
|
|
||||
if (cacheItem == null) |
|
||||
{ |
|
||||
var records = await GetNotificationsFormRepositoryAsync(); |
|
||||
var recordCaches = new List<NotificationDefinitionCacheItem>(); |
|
||||
|
|
||||
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<string>(); |
|
||||
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<List<NotificationDefinitionRecord>> GetNotificationsFormRepositoryAsync() |
|
||||
{ |
|
||||
var records = await NotificationDefinitionRecordRepository.GetListAsync(); |
|
||||
|
|
||||
return records; |
|
||||
} |
|
||||
} |
|
||||
@ -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<string, NotificationGroupDefinition> NotificationGroupDefinitions { get; } |
||||
|
protected IDictionary<string, NotificationDefinition> 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<string, NotificationGroupDefinition>(); |
||||
|
NotificationDefinitions = new Dictionary<string, NotificationDefinition>(); |
||||
|
} |
||||
|
|
||||
|
public Task FillAsync( |
||||
|
List<NotificationDefinitionGroupRecord> notificationGroupRecords, |
||||
|
List<NotificationDefinitionRecord> 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<NotificationDefinition> GetNotifications() |
||||
|
{ |
||||
|
return NotificationDefinitions.Values.ToList(); |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyList<NotificationGroupDefinition> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,49 +1,171 @@ |
|||||
using Microsoft.Extensions.Options; |
using Microsoft.Extensions.Caching.Distributed; |
||||
|
using Microsoft.Extensions.Options; |
||||
using System; |
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Caching; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
namespace LINGYUN.Abp.Notifications; |
namespace LINGYUN.Abp.Notifications; |
||||
|
|
||||
[Dependency(ReplaceServices = true)] |
[Dependency(ReplaceServices = true)] |
||||
public class DynamicNotificationDefinitionStore : IDynamicNotificationDefinitionStore, ITransientDependency |
public class DynamicNotificationDefinitionStore : IDynamicNotificationDefinitionStore, ITransientDependency |
||||
{ |
{ |
||||
private readonly AbpNotificationsManagementOptions _notificationsManagementOptions; |
protected INotificationDefinitionGroupRecordRepository NotificationGroupRepository { get; } |
||||
private readonly IDynamicNotificationDefinitionCache _dynamicNotificationDefinitionCache; |
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( |
public DynamicNotificationDefinitionStore( |
||||
IDynamicNotificationDefinitionCache dynamicNotificationDefinitionCache, |
INotificationDefinitionGroupRecordRepository notificationGroupRepository, |
||||
IOptions<AbpNotificationsManagementOptions> notificationsManagementOptions) |
INotificationDefinitionRecordRepository notificationRepository, |
||||
|
INotificationDefinitionSerializer notificationDefinitionSerializer, |
||||
|
IDynamicNotificationDefinitionStoreCache storeCache, |
||||
|
IDistributedCache distributedCache, |
||||
|
IOptions<AbpDistributedCacheOptions> cacheOptions, |
||||
|
IOptions<AbpNotificationsManagementOptions> notificationManagementOptions, |
||||
|
IAbpDistributedLock distributedLock) |
||||
{ |
{ |
||||
_dynamicNotificationDefinitionCache = dynamicNotificationDefinitionCache; |
NotificationGroupRepository = notificationGroupRepository; |
||||
_notificationsManagementOptions = notificationsManagementOptions.Value; |
NotificationRepository = notificationRepository; |
||||
|
NotificationDefinitionSerializer = notificationDefinitionSerializer; |
||||
|
StoreCache = storeCache; |
||||
|
DistributedCache = distributedCache; |
||||
|
DistributedLock = distributedLock; |
||||
|
NotificationManagementOptions = notificationManagementOptions.Value; |
||||
|
CacheOptions = cacheOptions.Value; |
||||
} |
} |
||||
|
|
||||
public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() |
public async virtual Task<NotificationDefinition> GetOrNullAsync(string name) |
||||
{ |
{ |
||||
if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) |
if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) |
||||
{ |
{ |
||||
return Array.Empty<NotificationGroupDefinition>(); |
return null; |
||||
|
} |
||||
|
|
||||
|
using (await StoreCache.SyncSemaphore.LockAsync()) |
||||
|
{ |
||||
|
await EnsureCacheIsUptoDateAsync(); |
||||
|
return StoreCache.GetNotificationOrNull(name); |
||||
} |
} |
||||
return await _dynamicNotificationDefinitionCache.GetGroupsAsync(); |
|
||||
} |
} |
||||
|
|
||||
public async virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync() |
public async virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync() |
||||
{ |
{ |
||||
if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) |
if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) |
||||
{ |
{ |
||||
return Array.Empty<NotificationDefinition>(); |
return Array.Empty<NotificationDefinition>(); |
||||
} |
} |
||||
return await _dynamicNotificationDefinitionCache.GetNotificationsAsync(); |
|
||||
|
using (await StoreCache.SyncSemaphore.LockAsync()) |
||||
|
{ |
||||
|
await EnsureCacheIsUptoDateAsync(); |
||||
|
return StoreCache.GetNotifications().ToImmutableList(); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
public async virtual Task<NotificationDefinition> GetOrNullAsync(string name) |
public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() |
||||
{ |
{ |
||||
if (!_notificationsManagementOptions.IsDynamicNotificationsStoreEnabled) |
if (!NotificationManagementOptions.IsDynamicNotificationsStoreEnabled) |
||||
{ |
{ |
||||
return null; |
return Array.Empty<NotificationGroupDefinition>(); |
||||
|
} |
||||
|
|
||||
|
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<string> 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"; |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,13 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications; |
|
||||
|
|
||||
public interface IDynamicNotificationDefinitionCache |
|
||||
{ |
|
||||
Task<NotificationDefinition> GetOrNullAsync(string name); |
|
||||
|
|
||||
Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync(); |
|
||||
|
|
||||
Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync(); |
|
||||
} |
|
||||
@ -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<NotificationDefinitionGroupRecord> webhookGroupRecords, |
||||
|
List<NotificationDefinitionRecord> webhookRecords); |
||||
|
|
||||
|
NotificationDefinition GetNotificationOrNull(string name); |
||||
|
|
||||
|
IReadOnlyList<NotificationDefinition> GetNotifications(); |
||||
|
|
||||
|
IReadOnlyList<NotificationGroupDefinition> GetGroups(); |
||||
|
} |
||||
Loading…
Reference in new issue