45 changed files with 2009 additions and 9 deletions
@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
</Weavers> |
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<Import Project="..\..\..\configureawait.props" /> |
||||
<Import Project="..\..\..\common.props" /> |
||||
|
||||
<PropertyGroup> |
||||
<TargetFramework>netstandard2.0</TargetFramework> |
||||
<RootNamespace /> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<PackageReference Include="Volo.Abp.EventBus" Version="$(VoloAbpVersion)" /> |
||||
<PackageReference Include="Volo.Abp.Json" Version="$(VoloAbpVersion)" /> |
||||
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(VoloAbpVersion)" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<ProjectReference Include="..\Sanhe.Abp.IdGenerator\Sanhe.Abp.IdGenerator.csproj" /> |
||||
<ProjectReference Include="..\Sanhe.Abp.RealTime\Sanhe.Abp.RealTime.csproj" /> |
||||
</ItemGroup> |
||||
|
||||
|
||||
</Project> |
@ -0,0 +1,46 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Sanhe.Abp.IdGenerator; |
||||
using Sanhe.Abp.RealTime; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Volo.Abp.BackgroundJobs; |
||||
using Volo.Abp.BackgroundWorkers; |
||||
using Volo.Abp.Json; |
||||
using Volo.Abp.Modularity; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
// TODO: 需要重命名 AbpNotificationsModule |
||||
[DependsOn( |
||||
typeof(AbpBackgroundWorkersModule), |
||||
typeof(AbpBackgroundJobsAbstractionsModule), |
||||
typeof(AbpIdGeneratorModule), |
||||
typeof(AbpJsonModule), |
||||
typeof(AbpRealTimeModule))] |
||||
public class AbpNotificationsModule : AbpModule |
||||
{ |
||||
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
{ |
||||
AutoAddDefinitionProviders(context.Services); |
||||
} |
||||
|
||||
private void AutoAddDefinitionProviders(IServiceCollection services) |
||||
{ |
||||
var definitionProviders = new List<Type>(); |
||||
|
||||
services.OnRegistred(context => |
||||
{ |
||||
if (typeof(INotificationDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
||||
{ |
||||
definitionProviders.Add(context.ImplementationType); |
||||
} |
||||
}); |
||||
|
||||
var preActions = services.GetPreConfigureActions<AbpNotificationsOptions>(); |
||||
Configure<AbpNotificationsOptions>(options => |
||||
{ |
||||
preActions.Configure(options); |
||||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,29 @@
|
||||
using Volo.Abp.Collections; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知选项 |
||||
/// </summary> |
||||
public class AbpNotificationsOptions |
||||
{ |
||||
/// <summary> |
||||
/// 自定义通知集合 |
||||
/// </summary> |
||||
public ITypeList<INotificationDefinitionProvider> DefinitionProviders { get; } |
||||
/// <summary> |
||||
/// 发布者集合 |
||||
/// </summary> |
||||
public ITypeList<INotificationPublishProvider> PublishProviders { get; } |
||||
/// <summary> |
||||
/// 可以自定义某个通知的格式 |
||||
/// </summary> |
||||
public NotificationDataMappingDictionary NotificationDataMappings { get; } |
||||
|
||||
public AbpNotificationsOptions() |
||||
{ |
||||
PublishProviders = new TypeList<INotificationPublishProvider>(); |
||||
DefinitionProviders = new TypeList<INotificationDefinitionProvider>(); |
||||
NotificationDataMappings = new NotificationDataMappingDictionary(); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
|
||||
using JetBrains.Annotations; |
||||
using Volo.Abp.Localization; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知定义上下文 |
||||
/// </summary> |
||||
public interface INotificationDefinitionContext |
||||
{ |
||||
/// <summary> |
||||
/// 添加通知组定义 |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
/// <param name="displayName"></param> |
||||
/// <param name="allowSubscriptionToClients"></param> |
||||
/// <returns></returns> |
||||
NotificationGroupDefinition AddGroup( |
||||
[NotNull] string name, |
||||
ILocalizableString displayName = null, |
||||
bool allowSubscriptionToClients = true); |
||||
/// <summary> |
||||
/// 获取通知组定义 |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
/// <returns></returns> |
||||
NotificationGroupDefinition GetGroupOrNull(string name); |
||||
/// <summary> |
||||
/// 移除通知组 |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
void RemoveGroup(string name); |
||||
} |
@ -0,0 +1,34 @@
|
||||
using JetBrains.Annotations; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知定义管理者接口 |
||||
/// </summary> |
||||
public interface INotificationDefinitionManager |
||||
{ |
||||
/// <summary> |
||||
/// 获取通知定义 |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
/// <returns></returns> |
||||
[NotNull] |
||||
NotificationDefinition Get([NotNull] string name); |
||||
/// <summary> |
||||
/// 获取所有通知定义 |
||||
/// </summary> |
||||
/// <returns></returns> |
||||
IReadOnlyList<NotificationDefinition> GetAll(); |
||||
/// <summary> |
||||
/// 获取通知定义,如果为空返回Null |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
/// <returns></returns> |
||||
NotificationDefinition GetOrNull(string name); |
||||
/// <summary> |
||||
/// 获取通知定义分组列表 |
||||
/// </summary> |
||||
/// <returns></returns> |
||||
IReadOnlyList<NotificationGroupDefinition> GetGroups(); |
||||
} |
@ -0,0 +1,14 @@
|
||||
namespace Sanhe.Abp.Notifications |
||||
{ |
||||
/// <summary> |
||||
/// 通知定义提供者接口 |
||||
/// </summary> |
||||
public interface INotificationDefinitionProvider |
||||
{ |
||||
/// <summary> |
||||
/// 定义 |
||||
/// </summary> |
||||
/// <param name="context">通知定义上下文</param> |
||||
void Define(INotificationDefinitionContext context); |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知发布提供者接口 |
||||
/// </summary> |
||||
public interface INotificationPublishProvider |
||||
{ |
||||
/// <summary> |
||||
/// 名称 |
||||
/// </summary> |
||||
string Name { get; } |
||||
/// <summary> |
||||
/// 发布通知 |
||||
/// </summary> |
||||
/// <param name="notification">通知信息</param> |
||||
/// <param name="identifiers">接收用户列表</param> |
||||
/// <returns></returns> |
||||
Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers); |
||||
} |
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace Sanhe.Abp.Notifications |
||||
{ |
||||
/// <summary> |
||||
/// 通知发布提供者管理 |
||||
/// </summary> |
||||
public interface INotificationPublishProviderManager |
||||
{ |
||||
/// <summary> |
||||
/// 提供者列表 |
||||
/// </summary> |
||||
List<INotificationPublishProvider> Providers { get; } |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 发送通知接口 |
||||
/// </summary> |
||||
public interface INotificationSender |
||||
{ |
||||
/// <summary> |
||||
/// 发送通知 |
||||
/// </summary> |
||||
/// <param name="name">名称</param> |
||||
/// <param name="data">数据</param> |
||||
/// <param name="userId">用户,为空标识发给所有订阅用户</param> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="severity">严重级别</param> |
||||
/// <returns>通知标识</returns> |
||||
Task<string> SendNofiterAsync( |
||||
string name, |
||||
NotificationData data, |
||||
UserIdentifier user = null, |
||||
Guid? tenantId = null, |
||||
NotificationSeverity severity = NotificationSeverity.Info); |
||||
|
||||
/// <summary> |
||||
/// 发送通知 |
||||
/// </summary> |
||||
/// <param name="name">名称</param> |
||||
/// <param name="data">数据</param> |
||||
/// <param name="users">用户列表,为空标识发给所有订阅用户</param> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="severity">严重级别</param> |
||||
/// <returns>通知标识</returns> |
||||
Task<string> SendNofitersAsync( |
||||
string name, |
||||
NotificationData data, |
||||
IEnumerable<UserIdentifier> users = null, |
||||
Guid? tenantId = null, |
||||
NotificationSeverity severity = NotificationSeverity.Info); |
||||
} |
@ -0,0 +1,127 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知相关储存处 |
||||
/// </summary> |
||||
public interface INotificationStore |
||||
{ |
||||
Task InsertUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task InsertUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteAllUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
IEnumerable<UserIdentifier> identifiers = null, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string userName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<bool> IsSubscribedAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task InsertNotificationAsync( |
||||
NotificationInfo notification, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteNotificationAsync( |
||||
NotificationInfo notification, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteNotificationAsync( |
||||
int batchCount, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task InsertUserNotificationAsync( |
||||
NotificationInfo notification, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task InsertUserNotificationsAsync( |
||||
NotificationInfo notification, |
||||
IEnumerable<Guid> userIds, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task DeleteUserNotificationAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
long notificationId, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<NotificationInfo> GetNotificationOrNullAsync( |
||||
Guid? tenantId, |
||||
long notificationId, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<List<NotificationInfo>> GetUserNotificationsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
NotificationReadState? readState = null, |
||||
int maxResultCount = 10, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<int> GetUserNotificationsCountAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string filter = "", |
||||
NotificationReadState? readState = null, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task<List<NotificationInfo>> GetUserNotificationsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string filter = "", |
||||
string sorting = nameof(NotificationInfo.CreationTime), |
||||
NotificationReadState? readState = null, |
||||
int skipCount = 1, |
||||
int maxResultCount = 10, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
Task ChangeUserNotificationReadStateAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
long notificationId, |
||||
NotificationReadState readState, |
||||
CancellationToken cancellationToken = default); |
||||
} |
@ -0,0 +1,123 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知订阅管理器 |
||||
/// </summary> |
||||
public interface INotificationSubscriptionManager |
||||
{ |
||||
/// <summary> |
||||
/// 是否已订阅 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="userId">用户标识</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task<bool> IsSubscribedAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 订阅通知 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="identifier">用户标识</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task SubscribeAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 订阅通知 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="identifiers">用户标识列表</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task SubscribeAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 取消所有用户订阅 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task UnsubscribeAllAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 取消订阅 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="identifier">用户标识</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task UnsubscribeAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 取消订阅 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="identifiers">用户标识列表</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <returns></returns> |
||||
Task UnsubscribeAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 获取通知被订阅用户列表 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="notificationName">通知名称</param> |
||||
/// <param name="identifiers">需要检查的用户列表</param> |
||||
/// <returns></returns> |
||||
Task<List<NotificationSubscriptionInfo>> GetUsersSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
IEnumerable<UserIdentifier> identifiers = null, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 获取用户订阅列表 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="userId">用户标识</param> |
||||
/// <returns></returns> |
||||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default); |
||||
|
||||
/// <summary> |
||||
/// 获取用户订阅列表 |
||||
/// </summary> |
||||
/// <param name="tenantId">租户</param> |
||||
/// <param name="userName">用户名</param> |
||||
/// <returns></returns> |
||||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string userName, |
||||
CancellationToken cancellationToken = default); |
||||
} |
@ -0,0 +1,115 @@
|
||||
using Microsoft.Extensions.Logging; |
||||
using Microsoft.Extensions.Logging.Abstractions; |
||||
using Microsoft.Extensions.Options; |
||||
using Sanhe.Abp.IdGenerator; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
||||
using Volo.Abp.EventBus.Distributed; |
||||
using Volo.Abp.Uow; |
||||
|
||||
namespace Sanhe.Abp.Notifications.Internal |
||||
{ |
||||
/// <summary> |
||||
/// 默认实现通过分布式事件发送通知 |
||||
/// 可替换实现来发送实时通知 |
||||
/// </summary> |
||||
public class NotificationSender : INotificationSender, ITransientDependency |
||||
{ |
||||
/// <summary> |
||||
/// Reference to <see cref="ILogger<NotificationSender>"/>. |
||||
/// </summary> |
||||
public ILogger<NotificationSender> Logger { get; set; } |
||||
/// <summary> |
||||
/// Reference to <see cref="IDistributedEventBus"/>. |
||||
/// </summary> |
||||
public IDistributedEventBus DistributedEventBus { get; } |
||||
/// <summary> |
||||
/// Reference to <see cref="IDistributedIdGenerator"/>. |
||||
/// </summary> |
||||
protected IDistributedIdGenerator DistributedIdGenerator { get; } |
||||
/// <summary> |
||||
/// Reference to <see cref="IUnitOfWorkManager"/>. |
||||
/// </summary> |
||||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
/// <summary> |
||||
/// 通知选项 |
||||
/// </summary> |
||||
protected AbpNotificationsOptions Options { get; } |
||||
|
||||
public NotificationSender( |
||||
IDistributedEventBus distributedEventBus, |
||||
IDistributedIdGenerator distributedIdGenerator, |
||||
IUnitOfWorkManager unitOfWorkManager, |
||||
IOptions<AbpNotificationsOptions> optionsAccessor) |
||||
{ |
||||
Options = optionsAccessor.Value; |
||||
DistributedEventBus = distributedEventBus; |
||||
DistributedIdGenerator = distributedIdGenerator; |
||||
UnitOfWorkManager = unitOfWorkManager; |
||||
Logger = NullLogger<NotificationSender>.Instance; |
||||
} |
||||
|
||||
public async Task<string> SendNofiterAsync( |
||||
string name, |
||||
NotificationData data, |
||||
UserIdentifier user = null, |
||||
Guid? tenantId = null, |
||||
NotificationSeverity severity = NotificationSeverity.Info) |
||||
{ |
||||
if (user == null) |
||||
{ |
||||
return await PublishNofiterAsync(name, data, null, tenantId, severity); |
||||
|
||||
} |
||||
else |
||||
{ |
||||
return await PublishNofiterAsync(name, data, new List<UserIdentifier> { user }, tenantId, severity); |
||||
} |
||||
} |
||||
|
||||
public async Task<string> SendNofitersAsync( |
||||
string name, |
||||
NotificationData data, |
||||
IEnumerable<UserIdentifier> users = null, |
||||
Guid? tenantId = null, |
||||
NotificationSeverity severity = NotificationSeverity.Info) |
||||
{ |
||||
return await PublishNofiterAsync(name, data, users, tenantId, severity); |
||||
} |
||||
|
||||
protected async Task<string> PublishNofiterAsync( |
||||
string name, |
||||
NotificationData data, |
||||
IEnumerable<UserIdentifier> users = null, |
||||
Guid? tenantId = null, |
||||
NotificationSeverity severity = NotificationSeverity.Info) |
||||
{ |
||||
var eto = new NotificationEto<NotificationData>(data) |
||||
{ |
||||
Id = DistributedIdGenerator.Create(), |
||||
TenantId = tenantId, |
||||
Users = users?.ToList(), |
||||
Name = name, |
||||
CreationTime = DateTime.Now, |
||||
Severity = severity |
||||
}; |
||||
|
||||
if (UnitOfWorkManager.Current != null) |
||||
{ |
||||
UnitOfWorkManager.Current.OnCompleted(async () => |
||||
{ |
||||
await DistributedEventBus.PublishAsync(eto); |
||||
}); |
||||
} |
||||
else |
||||
{ |
||||
await DistributedEventBus.PublishAsync(eto); |
||||
} |
||||
|
||||
return eto.Id.ToString(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,103 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
||||
|
||||
namespace Sanhe.Abp.Notifications.Internal |
||||
{ |
||||
internal class NotificationSubscriptionManager : INotificationSubscriptionManager, ITransientDependency |
||||
{ |
||||
private readonly INotificationStore _store; |
||||
|
||||
public NotificationSubscriptionManager(INotificationStore store) |
||||
{ |
||||
_store = store; |
||||
} |
||||
|
||||
public async virtual Task<List<NotificationSubscriptionInfo>> GetUsersSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
IEnumerable<UserIdentifier> identifiers = null, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return await _store.GetUserSubscriptionsAsync(tenantId, notificationName, identifiers, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return await _store.GetUserSubscriptionsAsync(tenantId, userId, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string userName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return await _store.GetUserSubscriptionsAsync(tenantId, userName, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task<bool> IsSubscribedAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return await _store.IsSubscribedAsync(tenantId, userId, notificationName, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task SubscribeAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
if (await IsSubscribedAsync(tenantId, identifier.UserId, notificationName, cancellationToken)) |
||||
{ |
||||
return; |
||||
} |
||||
await _store.InsertUserSubscriptionAsync(tenantId, identifier, notificationName, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task SubscribeAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
foreach (var identifier in identifiers) |
||||
{ |
||||
await SubscribeAsync(tenantId, identifier, notificationName, cancellationToken); |
||||
} |
||||
} |
||||
|
||||
public async virtual Task UnsubscribeAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
await _store.DeleteUserSubscriptionAsync(tenantId, identifier.UserId, notificationName, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task UnsubscribeAllAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
await _store.DeleteAllUserSubscriptionAsync(tenantId, notificationName, cancellationToken); |
||||
} |
||||
|
||||
public async virtual Task UnsubscribeAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
await _store.DeleteUserSubscriptionAsync(tenantId, identifiers, notificationName, cancellationToken); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,177 @@
|
||||
using Sanhe.Abp.RealTime.Localization; |
||||
using System; |
||||
using Volo.Abp.Data; |
||||
using Volo.Abp.EventBus; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知数据 |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// 把通知的标题和内容设计为 <see cref="LocalizableStringInfo"/> 让客户端自行本地化 |
||||
/// </remarks> |
||||
[Serializable] |
||||
[EventName("notifications")] |
||||
public class NotificationData : IHasExtraProperties |
||||
{ |
||||
/// <summary> |
||||
/// 用来标识是否需要本地化的信息 |
||||
/// </summary> |
||||
public const string LocalizerKey = "L"; |
||||
/// <summary> |
||||
/// <see cref="Type"/>类型完全名称 |
||||
/// </summary> |
||||
public virtual string Type => GetType().FullName; |
||||
/// <summary> |
||||
/// 获取或设置扩展属性 |
||||
/// </summary> |
||||
/// <param name="key"></param> |
||||
/// <returns></returns> |
||||
public object this[string key] { |
||||
get { |
||||
return this.GetProperty(key); |
||||
} |
||||
set { |
||||
this.SetProperty(key, value); |
||||
} |
||||
} |
||||
/// <summary> |
||||
/// 扩展属性 |
||||
/// </summary> |
||||
public ExtraPropertyDictionary ExtraProperties { get; set; } |
||||
|
||||
public NotificationData() |
||||
{ |
||||
ExtraProperties = new ExtraPropertyDictionary(); |
||||
this.SetDefaultsForExtraProperties(); |
||||
|
||||
TrySetData(LocalizerKey, false); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 写入本地化的消息数据 |
||||
/// </summary> |
||||
/// <param name="title"></param> |
||||
/// <param name="message"></param> |
||||
/// <param name="createTime"></param> |
||||
/// <param name="formUser"></param> |
||||
/// <param name="description"></param> |
||||
/// <returns></returns> |
||||
public NotificationData WriteLocalizedData( |
||||
LocalizableStringInfo title, |
||||
LocalizableStringInfo message, |
||||
DateTime createTime, |
||||
string formUser, |
||||
LocalizableStringInfo description = null) |
||||
{ |
||||
TrySetData("title", title); |
||||
TrySetData("message", message); |
||||
TrySetData("formUser", formUser); |
||||
TrySetData("createTime", createTime); |
||||
TrySetData(LocalizerKey, true); |
||||
|
||||
if (description != null) |
||||
{ |
||||
TrySetData("description", description); |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 写入标准数据 |
||||
/// </summary> |
||||
/// <param name="title">标题</param> |
||||
/// <param name="message">内容</param> |
||||
/// <param name="createTime">创建时间</param> |
||||
/// <param name="formUser">来源用户</param> |
||||
/// <param name="description">附加说明</param> |
||||
/// <returns></returns> |
||||
public NotificationData WriteStandardData(string title, string message, DateTime createTime, string formUser, string description = "") |
||||
{ |
||||
TrySetData("title", title); |
||||
TrySetData("message", message); |
||||
TrySetData("description", description); |
||||
TrySetData("formUser", formUser); |
||||
TrySetData("createTime", createTime); |
||||
TrySetData(LocalizerKey, false); |
||||
return this; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 写入标准数据 |
||||
/// </summary> |
||||
/// <param name="prefix">数据前缀</param> |
||||
/// <param name="key">标识</param> |
||||
/// <param name="value">数据内容</param> |
||||
/// <returns></returns> |
||||
public NotificationData WriteStandardData(string prefix, string key, object value) |
||||
{ |
||||
TrySetData(string.Concat(prefix, key), value); |
||||
TrySetData(LocalizerKey, false); |
||||
return this; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 转换为标准数据 |
||||
/// </summary> |
||||
/// <param name="sourceData">原始数据</param> |
||||
/// <returns></returns> |
||||
public static NotificationData ToStandardData(NotificationData sourceData) |
||||
{ |
||||
var data = new NotificationData(); |
||||
data.TrySetData("title", sourceData.TryGetData("title")); |
||||
data.TrySetData("message", sourceData.TryGetData("message")); |
||||
data.TrySetData("description", sourceData.TryGetData("description")); |
||||
data.TrySetData("formUser", sourceData.TryGetData("formUser")); |
||||
data.TrySetData("createTime", sourceData.TryGetData("createTime")); |
||||
data.TrySetData(LocalizerKey, sourceData.TryGetData(LocalizerKey)); |
||||
return data; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 转换为标准数据 |
||||
/// </summary> |
||||
/// <param name="prefix">数据前缀</param> |
||||
/// <param name="sourceData">原始数据</param> |
||||
/// <returns></returns> |
||||
public static NotificationData ToStandardData(string prefix, NotificationData sourceData) |
||||
{ |
||||
var data = ToStandardData(sourceData); |
||||
|
||||
foreach (var property in sourceData.ExtraProperties) |
||||
{ |
||||
if (property.Key.StartsWith(prefix)) |
||||
{ |
||||
var key = property.Key.Replace(prefix, ""); |
||||
data.TrySetData(key, property.Value); |
||||
} |
||||
} |
||||
return data; |
||||
} |
||||
|
||||
public object TryGetData(string key) |
||||
{ |
||||
return this.GetProperty(key); |
||||
} |
||||
|
||||
public void TrySetData(string key, object value) |
||||
{ |
||||
this.SetProperty(key, value); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 需要本地化 |
||||
/// </summary> |
||||
/// <returns></returns> |
||||
public bool NeedLocalizer() |
||||
{ |
||||
var localizer = TryGetData(LocalizerKey); |
||||
if (localizer != null && localizer is bool needLocalizer) |
||||
{ |
||||
return needLocalizer; |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,38 @@
|
||||
using Sanhe.Abp.RealTime.Localization; |
||||
using Newtonsoft.Json; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
public class NotificationDataConverter |
||||
{ |
||||
public static NotificationData Convert(NotificationData notificationData) |
||||
{ |
||||
if (notificationData != null) |
||||
{ |
||||
if (notificationData.NeedLocalizer()) |
||||
{ |
||||
// 潜在的空对象引用修复 |
||||
if (notificationData.ExtraProperties.TryGetValue("title", out var title) && title != null) |
||||
{ |
||||
var titleObj = JsonConvert.DeserializeObject<LocalizableStringInfo>(title.ToString()); |
||||
notificationData.TrySetData("title", titleObj); |
||||
} |
||||
if (notificationData.ExtraProperties.TryGetValue("message", out var message) && message != null) |
||||
{ |
||||
var messageObj = JsonConvert.DeserializeObject<LocalizableStringInfo>(message.ToString()); |
||||
notificationData.TrySetData("message", messageObj); |
||||
} |
||||
|
||||
if (notificationData.ExtraProperties.TryGetValue("description", out var description) && description != null) |
||||
{ |
||||
notificationData.TrySetData("description", JsonConvert.DeserializeObject<LocalizableStringInfo>(description.ToString())); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
notificationData = new NotificationData(); |
||||
} |
||||
return notificationData; |
||||
} |
||||
} |
@ -0,0 +1,63 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace Sanhe.Abp.Notifications |
||||
{ |
||||
public class NotificationDataMappingDictionary : Dictionary<string, List<NotificationDataMappingDictionaryItem>> |
||||
{ |
||||
public static string DefaultKey { get; set; } = "Default"; |
||||
|
||||
/// <summary> |
||||
/// 处理某个通知的数据 |
||||
/// 特定于一个提供程序 |
||||
/// </summary> |
||||
/// <param name="provider"></param> |
||||
/// <param name="name"></param> |
||||
/// <param name="func"></param> |
||||
public void Mapping(string provider, string name, Func<NotificationData, NotificationData> func) |
||||
{ |
||||
if (!ContainsKey(provider)) |
||||
{ |
||||
this[provider] = new List<NotificationDataMappingDictionaryItem>(); |
||||
} |
||||
|
||||
var mapItem = this[provider].FirstOrDefault(item => item.Name.Equals(name)); |
||||
|
||||
if (mapItem == null) |
||||
{ |
||||
this[provider].Add(new NotificationDataMappingDictionaryItem(name, func)); |
||||
} |
||||
else |
||||
{ |
||||
mapItem.Replace(func); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 处理所有通知的数据 |
||||
/// 特定于一个提供程序 |
||||
/// </summary> |
||||
/// <param name="provider"></param> |
||||
/// <param name="func"></param> |
||||
public void MappingDefault(string provider, Func<NotificationData, NotificationData> func) |
||||
{ |
||||
Mapping(provider, DefaultKey, func); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取需要处理数据的方法 |
||||
/// </summary> |
||||
/// <param name="name"></param> |
||||
/// <param name="provider"></param> |
||||
/// <returns></returns> |
||||
public NotificationDataMappingDictionaryItem GetMapItemOrDefault(string provider, string name) |
||||
{ |
||||
if (ContainsKey(provider)) |
||||
{ |
||||
return this[provider].GetOrNullDefault(name); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,30 @@
|
||||
using System; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
public class NotificationDataMappingDictionaryItem |
||||
{ |
||||
/// <summary> |
||||
/// 通知名称 |
||||
/// </summary> |
||||
public string Name { get; } |
||||
/// <summary> |
||||
/// 转换方法 |
||||
/// </summary> |
||||
public Func<NotificationData, NotificationData> MappingFunc { get; private set; } |
||||
|
||||
public NotificationDataMappingDictionaryItem(string name, Func<NotificationData, NotificationData> func) |
||||
{ |
||||
Name = name; |
||||
MappingFunc = func; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 替换 |
||||
/// </summary> |
||||
/// <param name="func"></param> |
||||
public void Replace(Func<NotificationData, NotificationData> func) |
||||
{ |
||||
MappingFunc = func; |
||||
} |
||||
} |
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace Sanhe.Abp.Notifications |
||||
{ |
||||
public static class NotificationDataMappingDictionaryItemExtensions |
||||
{ |
||||
public static NotificationDataMappingDictionaryItem GetOrNullDefault( |
||||
this IEnumerable<NotificationDataMappingDictionaryItem> items, |
||||
string name) |
||||
{ |
||||
var item = items.FirstOrDefault(i => i.Name.Equals(name)); |
||||
if (item == null) |
||||
{ |
||||
return items.FirstOrDefault(i => i.Name.Equals(NotificationDataMappingDictionary.DefaultKey)); |
||||
} |
||||
return item; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,94 @@
|
||||
using JetBrains.Annotations; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Volo.Abp; |
||||
using Volo.Abp.Localization; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知定义 |
||||
/// </summary> |
||||
public class NotificationDefinition |
||||
{ |
||||
/// <summary> |
||||
/// 通知名称 |
||||
/// </summary> |
||||
[NotNull] |
||||
public string Name { get; set; } |
||||
/// <summary> |
||||
/// 通知显示名称 |
||||
/// </summary> |
||||
[NotNull] |
||||
public ILocalizableString DisplayName { |
||||
get => _displayName; |
||||
set => _displayName = Check.NotNull(value, nameof(value)); |
||||
} |
||||
private ILocalizableString _displayName; |
||||
/// <summary> |
||||
/// 通知说明 |
||||
/// </summary> |
||||
[CanBeNull] |
||||
public ILocalizableString Description { get; set; } |
||||
/// <summary> |
||||
/// 允许客户端显示订阅 |
||||
/// </summary> |
||||
public bool AllowSubscriptionToClients { get; set; } |
||||
/// <summary> |
||||
/// 存活类型 |
||||
/// </summary> |
||||
public NotificationLifetime NotificationLifetime { get; set; } |
||||
/// <summary> |
||||
/// 通知类型 |
||||
/// </summary> |
||||
public NotificationType NotificationType { get; set; } |
||||
/// <summary> |
||||
/// 通知提供者 |
||||
/// </summary> |
||||
public List<string> Providers { get; } |
||||
/// <summary> |
||||
/// 额外属性 |
||||
/// </summary> |
||||
[NotNull] |
||||
public Dictionary<string, object> Properties { get; } |
||||
|
||||
public NotificationDefinition( |
||||
string name, |
||||
ILocalizableString displayName = null, |
||||
ILocalizableString description = null, |
||||
NotificationType notificationType = NotificationType.Application, |
||||
NotificationLifetime lifetime = NotificationLifetime.Persistent, |
||||
bool allowSubscriptionToClients = false) |
||||
{ |
||||
Name = name; |
||||
DisplayName = displayName ?? new FixedLocalizableString(name); |
||||
Description = description; |
||||
NotificationLifetime = lifetime; |
||||
NotificationType = notificationType; |
||||
AllowSubscriptionToClients = allowSubscriptionToClients; |
||||
|
||||
Providers = new List<string>(); |
||||
Properties = new Dictionary<string, object>(); |
||||
} |
||||
|
||||
public virtual NotificationDefinition WithProviders(params string[] providers) |
||||
{ |
||||
if (!providers.IsNullOrEmpty()) |
||||
{ |
||||
Providers.AddRange(providers); |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
public virtual NotificationDefinition WithProperty(string key, object value) |
||||
{ |
||||
Properties[key] = value; |
||||
return this; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return $"[{nameof(NotificationDefinition)} {Name}]"; |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
using JetBrains.Annotations; |
||||
using System.Collections.Generic; |
||||
using Volo.Abp; |
||||
using Volo.Abp.Localization; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
public class NotificationDefinitionContext : INotificationDefinitionContext |
||||
{ |
||||
internal Dictionary<string, NotificationGroupDefinition> Groups { get; } |
||||
|
||||
public NotificationDefinitionContext() |
||||
{ |
||||
Groups = new Dictionary<string, NotificationGroupDefinition>(); |
||||
} |
||||
|
||||
public NotificationGroupDefinition AddGroup( |
||||
[NotNull] string name, |
||||
ILocalizableString displayName = null, |
||||
bool allowSubscriptionToClients = true) |
||||
{ |
||||
Check.NotNull(name, nameof(name)); |
||||
|
||||
if (Groups.ContainsKey(name)) |
||||
{ |
||||
throw new AbpException($"There is already an existing notification group with name: {name}"); |
||||
} |
||||
|
||||
return Groups[name] = new NotificationGroupDefinition(name, displayName, allowSubscriptionToClients); |
||||
} |
||||
|
||||
public NotificationGroupDefinition GetGroupOrNull(string name) |
||||
{ |
||||
Check.NotNull(name, nameof(name)); |
||||
|
||||
if (!Groups.ContainsKey(name)) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
return Groups[name]; |
||||
} |
||||
|
||||
public void RemoveGroup(string name) |
||||
{ |
||||
Check.NotNull(name, nameof(name)); |
||||
|
||||
if (!Groups.ContainsKey(name)) |
||||
{ |
||||
throw new AbpException($"Undefined notification group: '{name}'."); |
||||
} |
||||
|
||||
Groups.Remove(name); |
||||
} |
||||
} |
@ -0,0 +1,113 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Options; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Immutable; |
||||
using System.Linq; |
||||
using Volo.Abp; |
||||
using Volo.Abp.DependencyInjection; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知定义管理者接口 |
||||
/// </summary> |
||||
public class NotificationDefinitionManager : INotificationDefinitionManager, ISingletonDependency |
||||
{ |
||||
protected AbpNotificationsOptions Options { get; } |
||||
|
||||
protected IDictionary<string, NotificationGroupDefinition> NotificationGroupDefinitions => _lazyNotificationGroupDefinitions.Value; |
||||
private readonly Lazy<Dictionary<string, NotificationGroupDefinition>> _lazyNotificationGroupDefinitions; |
||||
|
||||
protected IDictionary<string, NotificationDefinition> NotificationDefinitions => _lazyNotificationDefinitions.Value; |
||||
private readonly Lazy<Dictionary<string, NotificationDefinition>> _lazyNotificationDefinitions; |
||||
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory; |
||||
|
||||
public NotificationDefinitionManager( |
||||
IOptions<AbpNotificationsOptions> optionsAccessor, |
||||
IServiceScopeFactory serviceScopeFactory) |
||||
{ |
||||
_serviceScopeFactory = serviceScopeFactory; |
||||
Options = optionsAccessor.Value; |
||||
|
||||
_lazyNotificationDefinitions = new Lazy<Dictionary<string, NotificationDefinition>>( |
||||
CreateNotificationDefinitions, |
||||
isThreadSafe: true |
||||
); |
||||
|
||||
_lazyNotificationGroupDefinitions = new Lazy<Dictionary<string, NotificationGroupDefinition>>( |
||||
CreateNotificationGroupDefinitions, |
||||
isThreadSafe: true |
||||
); |
||||
} |
||||
|
||||
public virtual NotificationDefinition Get(string name) |
||||
{ |
||||
Check.NotNull(name, nameof(name)); |
||||
|
||||
var feature = GetOrNull(name); |
||||
|
||||
if (feature == null) |
||||
{ |
||||
throw new AbpException("Undefined notification: " + name); |
||||
} |
||||
|
||||
return feature; |
||||
} |
||||
|
||||
public virtual IReadOnlyList<NotificationDefinition> GetAll() |
||||
{ |
||||
return NotificationDefinitions.Values.ToImmutableList(); |
||||
} |
||||
|
||||
public virtual NotificationDefinition GetOrNull(string name) |
||||
{ |
||||
return NotificationDefinitions.GetOrDefault(name); |
||||
} |
||||
|
||||
public IReadOnlyList<NotificationGroupDefinition> GetGroups() |
||||
{ |
||||
return NotificationGroupDefinitions.Values.ToImmutableList(); |
||||
} |
||||
|
||||
protected virtual Dictionary<string, NotificationDefinition> CreateNotificationDefinitions() |
||||
{ |
||||
var notifications = new Dictionary<string, NotificationDefinition>(); |
||||
|
||||
foreach (var groupDefinition in NotificationGroupDefinitions.Values) |
||||
{ |
||||
foreach (var notification in groupDefinition.Notifications) |
||||
{ |
||||
if (notifications.ContainsKey(notification.Name)) |
||||
{ |
||||
throw new AbpException("Duplicate notification name: " + notification.Name); |
||||
} |
||||
|
||||
notifications[notification.Name] = notification; |
||||
} |
||||
} |
||||
|
||||
return notifications; |
||||
} |
||||
|
||||
protected virtual Dictionary<string, NotificationGroupDefinition> CreateNotificationGroupDefinitions() |
||||
{ |
||||
var context = new NotificationDefinitionContext(); |
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope()) |
||||
{ |
||||
var providers = Options |
||||
.DefinitionProviders |
||||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as INotificationDefinitionProvider) |
||||
.ToList(); |
||||
|
||||
foreach (var provider in providers) |
||||
{ |
||||
provider.Define(context); |
||||
} |
||||
} |
||||
|
||||
return context.Groups; |
||||
} |
||||
} |
@ -0,0 +1,15 @@
|
||||
using Volo.Abp.DependencyInjection; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知定义提供者抽象类 |
||||
/// </summary> |
||||
public abstract class NotificationDefinitionProvider : INotificationDefinitionProvider, ITransientDependency |
||||
{ |
||||
/// <summary> |
||||
/// 定义 |
||||
/// </summary> |
||||
/// <param name="context"></param> |
||||
public abstract void Define(INotificationDefinitionContext context); |
||||
} |
@ -0,0 +1,55 @@
|
||||
using Sanhe.Abp.RealTime; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Volo.Abp.EventBus; |
||||
using Volo.Abp.MultiTenancy; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知事件传输对象 |
||||
/// </summary> |
||||
/// <typeparam name="T"></typeparam> |
||||
[Serializable] |
||||
[GenericEventName(Prefix = "abp.realtime.")] |
||||
public class NotificationEto<T> : RealTimeEto<T>, IMultiTenant |
||||
{ |
||||
/// <summary> |
||||
/// 通知标识 |
||||
/// 自动计算 |
||||
/// </summary> |
||||
public long Id { get; set; } |
||||
/// <summary> |
||||
/// 租户 |
||||
/// </summary> |
||||
public Guid? TenantId { get; set; } |
||||
/// <summary> |
||||
/// 通知名称 |
||||
/// </summary> |
||||
public string Name { get; set; } |
||||
/// <summary> |
||||
/// 创建时间 |
||||
/// </summary> |
||||
public DateTime CreationTime { get; set; } |
||||
/// <summary> |
||||
/// 紧急级别 |
||||
/// </summary> |
||||
public NotificationSeverity Severity { get; set; } |
||||
/// <summary> |
||||
/// 指定的接收用户信息集合 |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// 注:<br/> |
||||
/// 如果指定了用户列表,应该在事件订阅程序中通过此集合过滤订阅用户<br/> |
||||
/// 如果未指定用户列表,应该在事件订阅程序中过滤所有订阅此通知的用户 |
||||
/// </remarks> |
||||
public List<UserIdentifier> Users { get; set; } = new List<UserIdentifier>(); |
||||
|
||||
public NotificationEto() : base() |
||||
{ |
||||
} |
||||
|
||||
public NotificationEto(T data) : base(data) |
||||
{ |
||||
} |
||||
} |
@ -0,0 +1,84 @@
|
||||
using JetBrains.Annotations; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Immutable; |
||||
using Volo.Abp; |
||||
using Volo.Abp.Localization; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知组定义 |
||||
/// </summary> |
||||
public class NotificationGroupDefinition |
||||
{ |
||||
/// <summary> |
||||
/// 通知组名称 |
||||
/// </summary> |
||||
[NotNull] |
||||
public string Name { get; set; } |
||||
/// <summary> |
||||
/// 通知组显示名称 |
||||
/// </summary> |
||||
[NotNull] |
||||
public ILocalizableString DisplayName { |
||||
get => _displayName; |
||||
set => _displayName = Check.NotNull(value, nameof(value)); |
||||
} |
||||
private ILocalizableString _displayName; |
||||
/// <summary> |
||||
/// 通知组说明 |
||||
/// </summary> |
||||
[CanBeNull] |
||||
public ILocalizableString Description { get; set; } |
||||
/// <summary> |
||||
/// 允许客户端显示订阅 |
||||
/// </summary> |
||||
public bool AllowSubscriptionToClients { get; set; } |
||||
|
||||
public IReadOnlyList<NotificationDefinition> Notifications => _notifications.ToImmutableList(); |
||||
private readonly List<NotificationDefinition> _notifications; |
||||
|
||||
protected internal NotificationGroupDefinition( |
||||
string name, |
||||
ILocalizableString displayName = null, |
||||
bool allowSubscriptionToClients = false) |
||||
{ |
||||
Name = name; |
||||
DisplayName = displayName ?? new FixedLocalizableString(Name); |
||||
AllowSubscriptionToClients = allowSubscriptionToClients; |
||||
|
||||
_notifications = new List<NotificationDefinition>(); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 添加通知 |
||||
/// </summary> |
||||
/// <param name="name">通知组名称</param> |
||||
/// <param name="displayName">通知组显示名称</param> |
||||
/// <param name="description">通知组说明</param> |
||||
/// <param name="notificationType">通知类型</param> |
||||
/// <param name="lifetime">通知存活时间</param> |
||||
/// <param name="allowSubscriptionToClients">允许客户端显示订阅</param> |
||||
/// <returns></returns> |
||||
public virtual NotificationDefinition AddNotification( |
||||
string name, |
||||
ILocalizableString displayName = null, |
||||
ILocalizableString description = null, |
||||
NotificationType notificationType = NotificationType.Application, |
||||
NotificationLifetime lifetime = NotificationLifetime.Persistent, |
||||
bool allowSubscriptionToClients = false) |
||||
{ |
||||
var notification = new NotificationDefinition( |
||||
name, |
||||
displayName, |
||||
description, |
||||
notificationType, |
||||
lifetime, |
||||
allowSubscriptionToClients |
||||
); |
||||
|
||||
_notifications.Add(notification); |
||||
|
||||
return notification; |
||||
} |
||||
} |
@ -0,0 +1,73 @@
|
||||
using System; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知信息 |
||||
/// </summary> |
||||
public class NotificationInfo |
||||
{ |
||||
/// <summary> |
||||
/// 租户Id |
||||
/// </summary> |
||||
public Guid? TenantId { get; set; } |
||||
/// <summary> |
||||
/// 名称 |
||||
/// </summary> |
||||
public string Name { get; set; } |
||||
/// <summary> |
||||
/// Id |
||||
/// </summary> |
||||
public string Id { get; set; } |
||||
/// <summary> |
||||
/// 通知数据 |
||||
/// </summary> |
||||
public NotificationData Data { get; set; } |
||||
/// <summary> |
||||
/// 创建时间 |
||||
/// </summary> |
||||
public DateTime CreationTime { get; set; } |
||||
/// <summary> |
||||
/// 通知存活时间 |
||||
/// </summary> |
||||
public NotificationLifetime Lifetime { get; set; } |
||||
/// <summary> |
||||
/// 通知类型 |
||||
/// </summary> |
||||
public NotificationType Type { get; set; } |
||||
/// <summary> |
||||
/// 通知严重级别 |
||||
/// </summary> |
||||
public NotificationSeverity Severity { get; set; } |
||||
|
||||
public NotificationInfo() |
||||
{ |
||||
Data = new NotificationData(); |
||||
Lifetime = NotificationLifetime.Persistent; |
||||
Type = NotificationType.Application; |
||||
Severity = NotificationSeverity.Info; |
||||
|
||||
CreationTime = DateTime.Now; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 设置Id |
||||
/// </summary> |
||||
/// <param name="id"></param> |
||||
public void SetId(long id) |
||||
{ |
||||
if (Id.IsNullOrWhiteSpace()) |
||||
{ |
||||
Id = id.ToString(); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取Id |
||||
/// </summary> |
||||
/// <returns></returns> |
||||
public long GetId() |
||||
{ |
||||
return long.Parse(Id); |
||||
} |
||||
} |
@ -0,0 +1,17 @@
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知存活时间 |
||||
/// 发送之后取消用户订阅,类似于微信小程序 |
||||
/// </summary> |
||||
public enum NotificationLifetime |
||||
{ |
||||
/// <summary> |
||||
/// 持久化 |
||||
/// </summary> |
||||
Persistent = 0, |
||||
/// <summary> |
||||
/// 一次性 |
||||
/// </summary> |
||||
OnlyOne = 1 |
||||
} |
@ -0,0 +1,24 @@
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 内置通知提供者 |
||||
/// </summary> |
||||
public static class NotificationProviderNames |
||||
{ |
||||
/// <summary> |
||||
/// SignalR 实时通知 |
||||
/// </summary> |
||||
public const string SignalR = "SignalR"; |
||||
/// <summary> |
||||
/// 短信通知 |
||||
/// </summary> |
||||
public const string Sms = "Sms"; |
||||
/// <summary> |
||||
/// 邮件通知 |
||||
/// </summary> |
||||
public const string Emailing = "Emailing"; |
||||
/// <summary> |
||||
/// 微信小程序模板通知 |
||||
/// </summary> |
||||
public const string WechatMiniProgram = "WeChat.MiniProgram"; |
||||
} |
@ -0,0 +1,68 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Logging; |
||||
using Microsoft.Extensions.Logging.Abstractions; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
||||
using Volo.Abp.Threading; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知发布提供者抽象类 |
||||
/// </summary> |
||||
public abstract class NotificationPublishProvider : INotificationPublishProvider, ITransientDependency |
||||
{ |
||||
public abstract string Name { get; } |
||||
|
||||
protected IServiceProvider ServiceProvider { get; } |
||||
|
||||
protected readonly object ServiceProviderLock = new(); |
||||
|
||||
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); |
||||
private ILoggerFactory _loggerFactory; |
||||
|
||||
protected ILogger Logger => LazyLogger.Value; |
||||
private Lazy<ILogger> LazyLogger => new(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); |
||||
|
||||
|
||||
protected TService LazyGetRequiredService<TService>(ref TService reference) |
||||
{ |
||||
if (reference == null) |
||||
{ |
||||
lock (ServiceProviderLock) |
||||
{ |
||||
if (reference == null) |
||||
{ |
||||
reference = ServiceProvider.GetRequiredService<TService>(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return reference; |
||||
} |
||||
|
||||
public ICancellationTokenProvider CancellationTokenProvider { get; set; } |
||||
|
||||
protected NotificationPublishProvider(IServiceProvider serviceProvider) |
||||
{ |
||||
ServiceProvider = serviceProvider; |
||||
CancellationTokenProvider = NullCancellationTokenProvider.Instance; |
||||
} |
||||
|
||||
public async Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers) |
||||
{ |
||||
await PublishAsync(notification, identifiers, CancellationTokenProvider.Token); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 重写实现通知发布 |
||||
/// </summary> |
||||
/// <param name="notification"></param> |
||||
/// <param name="identifiers"></param> |
||||
/// <param name="cancellationToken"></param> |
||||
/// <returns></returns> |
||||
protected abstract Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers, CancellationToken cancellationToken = default); |
||||
} |
@ -0,0 +1,32 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Options; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using Volo.Abp.DependencyInjection; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
public class NotificationPublishProviderManager : INotificationPublishProviderManager, ISingletonDependency |
||||
{ |
||||
public List<INotificationPublishProvider> Providers => _lazyProviders.Value; |
||||
|
||||
protected AbpNotificationsOptions Options { get; } |
||||
|
||||
private readonly Lazy<List<INotificationPublishProvider>> _lazyProviders; |
||||
|
||||
public NotificationPublishProviderManager( |
||||
IServiceProvider serviceProvider, |
||||
IOptions<AbpNotificationsOptions> optionsAccessor) |
||||
{ |
||||
Options = optionsAccessor.Value; |
||||
|
||||
_lazyProviders = new Lazy<List<INotificationPublishProvider>>( |
||||
() => Options |
||||
.PublishProviders |
||||
.Select(type => serviceProvider.GetRequiredService(type) as INotificationPublishProvider) |
||||
.ToList(), |
||||
true |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 读取状态 |
||||
/// </summary> |
||||
public enum NotificationReadState |
||||
{ |
||||
/// <summary> |
||||
/// 已读 |
||||
/// </summary> |
||||
Read = 0, |
||||
/// <summary> |
||||
/// 未读 |
||||
/// </summary> |
||||
UnRead = 1 |
||||
} |
@ -0,0 +1,28 @@
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 严重级别 |
||||
/// </summary> |
||||
public enum NotificationSeverity : sbyte |
||||
{ |
||||
/// <summary> |
||||
/// 成功 |
||||
/// </summary> |
||||
Success = 0, |
||||
/// <summary> |
||||
/// 信息 |
||||
/// </summary> |
||||
Info = 10, |
||||
/// <summary> |
||||
/// 警告 |
||||
/// </summary> |
||||
Warn = 20, |
||||
/// <summary> |
||||
/// 错误 |
||||
/// </summary> |
||||
Error = 30, |
||||
/// <summary> |
||||
/// 致命错误 |
||||
/// </summary> |
||||
Fatal = 40 |
||||
} |
@ -0,0 +1,26 @@
|
||||
using System; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知订阅信息 |
||||
/// </summary> |
||||
public class NotificationSubscriptionInfo |
||||
{ |
||||
/// <summary> |
||||
/// 租户Id |
||||
/// </summary> |
||||
public Guid? TenantId { get; set; } |
||||
/// <summary> |
||||
/// 用户Id |
||||
/// </summary> |
||||
public Guid UserId { get; set; } |
||||
/// <summary> |
||||
/// 用户名 |
||||
/// </summary> |
||||
public string UserName { get; set; } |
||||
/// <summary> |
||||
/// 通知名 |
||||
/// </summary> |
||||
public string NotificationName { get; set; } |
||||
} |
@ -0,0 +1,20 @@
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 通知类型 |
||||
/// </summary> |
||||
public enum NotificationType |
||||
{ |
||||
/// <summary> |
||||
/// 应用(对应租户) |
||||
/// </summary> |
||||
Application = 0, |
||||
/// <summary> |
||||
/// 系统(对应宿主) |
||||
/// </summary> |
||||
System = 10, |
||||
/// <summary> |
||||
/// 用户(对应用户) |
||||
/// </summary> |
||||
User = 20 |
||||
} |
@ -0,0 +1,189 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 空实现通知相关储存处 |
||||
/// </summary> |
||||
[Dependency(TryRegister = true)] |
||||
public class NullNotificationStore : INotificationStore, ISingletonDependency |
||||
{ |
||||
public Task ChangeUserNotificationReadStateAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
long notificationId, |
||||
NotificationReadState readState, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteAllUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteNotificationAsync( |
||||
NotificationInfo notification, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteNotificationAsync( |
||||
int batchCount, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteUserNotificationAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
long notificationId, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task DeleteUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task<NotificationInfo> GetNotificationOrNullAsync( |
||||
Guid? tenantId, |
||||
long notificationId, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new NotificationInfo()); |
||||
} |
||||
|
||||
public Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string notificationName, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new List<NotificationSubscriptionInfo>()); |
||||
} |
||||
|
||||
public Task<List<NotificationInfo>> GetUserNotificationsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
NotificationReadState? readState = null, |
||||
int maxResultCount = 10, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new List<NotificationInfo>()); |
||||
} |
||||
|
||||
public Task<int> GetUserNotificationsCountAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string filter = "", |
||||
NotificationReadState? readState = null, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(0); |
||||
} |
||||
|
||||
public Task<List<NotificationInfo>> GetUserNotificationsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string filter = "", |
||||
string sorting = nameof(NotificationInfo.CreationTime), |
||||
NotificationReadState? readState = null, |
||||
int skipCount = 1, |
||||
int maxResultCount = 10, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new List<NotificationInfo>()); |
||||
} |
||||
|
||||
public Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new List<NotificationSubscriptionInfo>()); |
||||
} |
||||
|
||||
public Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync( |
||||
Guid? tenantId, |
||||
string userName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(new List<NotificationSubscriptionInfo>()); |
||||
} |
||||
|
||||
public Task InsertNotificationAsync( |
||||
NotificationInfo notification, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task InsertUserNotificationAsync( |
||||
NotificationInfo notification, |
||||
Guid userId, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task InsertUserNotificationsAsync( |
||||
NotificationInfo notification, |
||||
IEnumerable<Guid> userIds, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task InsertUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
UserIdentifier identifier, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task InsertUserSubscriptionAsync( |
||||
Guid? tenantId, |
||||
IEnumerable<UserIdentifier> identifiers, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task<bool> IsSubscribedAsync( |
||||
Guid? tenantId, |
||||
Guid userId, |
||||
string notificationName, |
||||
CancellationToken cancellationToken = default) |
||||
{ |
||||
return Task.FromResult(false); |
||||
} |
||||
} |
@ -0,0 +1,24 @@
|
||||
using System; |
||||
|
||||
namespace Sanhe.Abp.Notifications; |
||||
|
||||
/// <summary> |
||||
/// 用户信息 |
||||
/// </summary> |
||||
public class UserIdentifier |
||||
{ |
||||
/// <summary> |
||||
/// 用户标识 |
||||
/// </summary> |
||||
public Guid UserId { get; set; } |
||||
/// <summary> |
||||
/// 用户名 |
||||
/// </summary> |
||||
public string UserName { get; set; } |
||||
|
||||
public UserIdentifier(Guid userId, string userName) |
||||
{ |
||||
UserId = userId; |
||||
UserName = userName; |
||||
} |
||||
} |
Loading…
Reference in new issue