31 changed files with 750 additions and 29 deletions
@ -0,0 +1,33 @@ |
|||
namespace LINGYUN.Abp.Notifications; |
|||
public static class NotificationDataExtensions |
|||
{ |
|||
private const string Prefix = "push-plus:"; |
|||
private const string WebhookKey = Prefix + "webhook"; |
|||
private const string CallbackUrlKey = Prefix + "callback"; |
|||
|
|||
public static void SetWebhook( |
|||
this NotificationData notificationData, |
|||
string url) |
|||
{ |
|||
notificationData.TrySetData(WebhookKey, url); |
|||
} |
|||
|
|||
public static string GetWebhookOrNull( |
|||
this NotificationData notificationData) |
|||
{ |
|||
return notificationData.TryGetData(WebhookKey)?.ToString(); |
|||
} |
|||
|
|||
public static void SetCallbackUrl( |
|||
this NotificationData notificationData, |
|||
string callbackUrl) |
|||
{ |
|||
notificationData.TrySetData(CallbackUrlKey, callbackUrl); |
|||
} |
|||
|
|||
public static string GetCallbackUrlOrNull( |
|||
this NotificationData notificationData) |
|||
{ |
|||
return notificationData.TryGetData(CallbackUrlKey)?.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
namespace LINGYUN.Abp.Notifications; |
|||
public static class NotificationDataExtensions |
|||
{ |
|||
private const string Prefix = "wx-pusher:"; |
|||
private const string UrlKey = Prefix + "url"; |
|||
|
|||
public static void SetUrl( |
|||
this NotificationData notificationData, |
|||
string url) |
|||
{ |
|||
notificationData.TrySetData(UrlKey, url); |
|||
} |
|||
|
|||
public static string GetUrlOrNull( |
|||
this NotificationData notificationData) |
|||
{ |
|||
return notificationData.TryGetData(UrlKey)?.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
using Microsoft.Extensions.Localization; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.TextTemplating; |
|||
|
|||
namespace LINGYUN.Abp.Notifications; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class FakeNotificationSender : INotificationSender, ITransientDependency |
|||
{ |
|||
public ILogger<FakeNotificationSender> Logger { get; set; } |
|||
|
|||
protected AbpNotificationOptions Options { get; } |
|||
|
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
|
|||
protected ITemplateRenderer TemplateRenderer { get; } |
|||
|
|||
protected INotificationStore NotificationStore { get; } |
|||
|
|||
protected IStringLocalizerFactory StringLocalizerFactory { get; } |
|||
|
|||
protected INotificationDefinitionManager NotificationDefinitionManager { get; } |
|||
|
|||
protected INotificationSubscriptionManager NotificationSubscriptionManager { get; } |
|||
|
|||
protected INotificationPublishProviderManager NotificationPublishProviderManager { get; } |
|||
|
|||
public FakeNotificationSender( |
|||
IJsonSerializer jsonSerializer, |
|||
ITemplateRenderer templateRenderer, |
|||
IStringLocalizerFactory stringLocalizerFactory, |
|||
IOptions<AbpNotificationOptions> options, |
|||
INotificationStore notificationStore, |
|||
INotificationDefinitionManager notificationDefinitionManager, |
|||
INotificationSubscriptionManager notificationSubscriptionManager, |
|||
INotificationPublishProviderManager notificationPublishProviderManager) |
|||
{ |
|||
Options = options.Value; |
|||
JsonSerializer = jsonSerializer; |
|||
TemplateRenderer = templateRenderer; |
|||
StringLocalizerFactory = stringLocalizerFactory; |
|||
NotificationStore = notificationStore; |
|||
NotificationDefinitionManager = notificationDefinitionManager; |
|||
NotificationSubscriptionManager = notificationSubscriptionManager; |
|||
NotificationPublishProviderManager = notificationPublishProviderManager; |
|||
|
|||
Logger = NullLogger<FakeNotificationSender>.Instance; |
|||
} |
|||
|
|||
public async virtual Task<string> SendNofiterAsync( |
|||
string name, |
|||
NotificationData data, |
|||
IEnumerable<UserIdentifier> users = null, |
|||
Guid? tenantId = null, |
|||
NotificationSeverity severity = NotificationSeverity.Info) |
|||
{ |
|||
var notification = NotificationDefinitionManager.GetOrNull(name); |
|||
if (notification == null) |
|||
{ |
|||
return ""; |
|||
} |
|||
|
|||
var notificationInfo = new NotificationInfo |
|||
{ |
|||
Name = notification.Name, |
|||
CreationTime = DateTime.Now, |
|||
Data = data, |
|||
Severity = severity, |
|||
Lifetime = notification.NotificationLifetime, |
|||
TenantId = tenantId, |
|||
Type = notification.NotificationType |
|||
}; |
|||
notificationInfo.SetId(DateTimeOffset.Now.ToUnixTimeMilliseconds()); |
|||
|
|||
notificationInfo.Data = NotificationDataConverter.Convert(notificationInfo.Data); |
|||
|
|||
Logger.LogDebug($"Persistent notification {notificationInfo.Name}"); |
|||
|
|||
await NotificationStore.InsertNotificationAsync(notificationInfo); |
|||
|
|||
var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers); |
|||
|
|||
if (notification.Providers.Any()) |
|||
{ |
|||
providers = providers.Where(p => notification.Providers.Contains(p.Name)); |
|||
} |
|||
|
|||
await PublishFromProvidersAsync( |
|||
providers, |
|||
users ?? new List<UserIdentifier>(), |
|||
notificationInfo); |
|||
|
|||
return notificationInfo.Id; |
|||
} |
|||
|
|||
public async virtual Task<string> SendNofiterAsync( |
|||
string name, |
|||
NotificationTemplate template, |
|||
IEnumerable<UserIdentifier> users = null, |
|||
Guid? tenantId = null, |
|||
NotificationSeverity severity = NotificationSeverity.Info) |
|||
{ |
|||
var notification = NotificationDefinitionManager.GetOrNull(name); |
|||
if (notification == null) |
|||
{ |
|||
return ""; |
|||
} |
|||
|
|||
var notificationInfo = new NotificationInfo |
|||
{ |
|||
Name = notification.Name, |
|||
TenantId = tenantId, |
|||
Severity = severity, |
|||
Type = notification.NotificationType, |
|||
CreationTime = DateTime.Now, |
|||
Lifetime = notification.NotificationLifetime, |
|||
}; |
|||
notificationInfo.SetId(DateTimeOffset.Now.ToUnixTimeMilliseconds()); |
|||
|
|||
var title = notification.DisplayName.Localize(StringLocalizerFactory); |
|||
|
|||
var message = await TemplateRenderer.RenderAsync( |
|||
templateName: name, |
|||
model: template.ExtraProperties); |
|||
|
|||
var notificationData = new NotificationData(); |
|||
notificationData.WriteStandardData( |
|||
title: title, |
|||
message: message, |
|||
createTime: notificationInfo.CreationTime, |
|||
formUser: "Fake User"); |
|||
notificationData.ExtraProperties.AddIfNotContains(template.ExtraProperties); |
|||
|
|||
notificationInfo.Data = notificationData; |
|||
|
|||
Logger.LogDebug($"Persistent notification {notificationInfo.Name}"); |
|||
|
|||
// 持久化通知
|
|||
await NotificationStore.InsertNotificationAsync(notificationInfo); |
|||
|
|||
var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers); |
|||
|
|||
// 过滤用户指定提供者
|
|||
if (notification.Providers.Any()) |
|||
{ |
|||
providers = providers.Where(p => notification.Providers.Contains(p.Name)); |
|||
} |
|||
|
|||
await PublishFromProvidersAsync( |
|||
providers, |
|||
users ?? new List<UserIdentifier>(), |
|||
notificationInfo); |
|||
|
|||
return notificationInfo.Id; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 指定提供者发布通知
|
|||
/// </summary>
|
|||
/// <param name="providers">提供者列表</param>
|
|||
/// <param name="notificationInfo">通知信息</param>
|
|||
/// <returns></returns>
|
|||
protected async Task PublishFromProvidersAsync( |
|||
IEnumerable<INotificationPublishProvider> providers, |
|||
IEnumerable<UserIdentifier> users, |
|||
NotificationInfo notificationInfo) |
|||
{ |
|||
foreach (var provider in providers) |
|||
{ |
|||
await PublishAsync(provider, notificationInfo, users); |
|||
} |
|||
} |
|||
/// <summary>
|
|||
/// 发布通知
|
|||
/// </summary>
|
|||
/// <param name="provider">通知发布者</param>
|
|||
/// <param name="notificationInfo">通知信息</param>
|
|||
/// <param name="subscriptionUserIdentifiers">订阅用户列表</param>
|
|||
/// <returns></returns>
|
|||
protected async Task PublishAsync( |
|||
INotificationPublishProvider provider, |
|||
NotificationInfo notificationInfo, |
|||
IEnumerable<UserIdentifier> subscriptionUserIdentifiers) |
|||
{ |
|||
Logger.LogDebug($"Sending notification with provider {provider.Name}"); |
|||
var notifacationDataMapping = Options.NotificationDataMappings |
|||
.GetMapItemOrDefault(provider.Name, notificationInfo.Name); |
|||
if (notifacationDataMapping != null) |
|||
{ |
|||
notificationInfo.Data = notifacationDataMapping.MappingFunc(notificationInfo.Data); |
|||
} |
|||
// 发布
|
|||
await provider.PublishAsync(notificationInfo, subscriptionUserIdentifiers); |
|||
|
|||
Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful"); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<IsPackable>false</IsPackable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\modules\wx-pusher\LINGYUN.Abp.Notifications.WxPusher\LINGYUN.Abp.Notifications.WxPusher.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WxPusher.Tests\LINGYUN.Abp.WxPusher.Tests.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Tests\LINGYUN.Abp.Notifications.Tests.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
using LINGYUN.Abp.Tests; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WxPusher; |
|||
|
|||
public abstract class AbpNotificationsWxPusherTestBase : AbpTestsBase<AbpNotificationsWxPusherTestModule> |
|||
{ |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using LINGYUN.Abp.WxPusher; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WxPusher; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpNotificationsWxPusherModule), |
|||
typeof(AbpWxPusherTestModule), |
|||
typeof(AbpNotificationsTestsModule))] |
|||
public class AbpNotificationsWxPusherTestModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WxPusher; |
|||
|
|||
public class NotificationSenderTests : AbpNotificationsWxPusherTestBase |
|||
{ |
|||
protected INotificationSender NotificationSender { get; } |
|||
|
|||
public NotificationSenderTests() |
|||
{ |
|||
NotificationSender = GetRequiredService<INotificationSender>(); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData( |
|||
"Title from the Xunit unit test", |
|||
"Text content from the Xunit unit test. \r\n Click the link at the top to redirect baidu site.")] |
|||
public async Task Send_Text_Test( |
|||
string title, |
|||
string message) |
|||
{ |
|||
var notificationData = new NotificationData(); |
|||
notificationData.WriteStandardData( |
|||
title, |
|||
message, |
|||
DateTime.Now, |
|||
"xUnit Test"); |
|||
notificationData.SetUrl("https://www.baidu.com/"); |
|||
|
|||
await NotificationSender.SendNofiterAsync( |
|||
NotificationsTestsNames.Test1, |
|||
notificationData); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData( |
|||
"Title from the Xunit unit test", |
|||
"<h>Html content from the Xunit unit test.</h> <br /> <a href=\"https://www.baidu.com/\">Click to redirect baidu site.</a>")] |
|||
public async Task Send_Html_Test( |
|||
string title, |
|||
string message) |
|||
{ |
|||
var notificationData = new NotificationData(); |
|||
notificationData.WriteStandardData( |
|||
title, |
|||
message, |
|||
DateTime.Now, |
|||
"xUnit Test"); |
|||
notificationData.SetUrl("https://www.baidu.com/"); |
|||
|
|||
await NotificationSender.SendNofiterAsync( |
|||
NotificationsTestsNames.Test2, |
|||
notificationData); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData( |
|||
"Title from the Xunit unit test", |
|||
"**Markdown content from the Xunit unit test.** <br /> <a href=\"https://www.baidu.com/\">Click to redirect baidu site.</a>")] |
|||
public async Task Send_Markdown_Test( |
|||
string title, |
|||
string message) |
|||
{ |
|||
var notificationData = new NotificationData(); |
|||
notificationData.WriteStandardData( |
|||
title, |
|||
message, |
|||
DateTime.Now, |
|||
"xUnit Test"); |
|||
notificationData.SetUrl("https://www.baidu.com/"); |
|||
|
|||
await NotificationSender.SendNofiterAsync( |
|||
NotificationsTestsNames.Test3, |
|||
notificationData); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using LINGYUN.Abp.WxPusher.Messages; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WxPusher; |
|||
|
|||
public class NotificationsWxPusherTestsDefinitionProvider : NotificationDefinitionProvider |
|||
{ |
|||
public override void Define(INotificationDefinitionContext context) |
|||
{ |
|||
var group = context.GetGroupOrNull(NotificationsTestsNames.GroupName); |
|||
|
|||
var nt1 = group.Notifications.FirstOrDefault(n => n.Name.Equals(NotificationsTestsNames.Test1)); |
|||
nt1.WithProviders(WxPusherNotificationPublishProvider.ProviderName) |
|||
.WithContentType(MessageContentType.Text) |
|||
.WithTopics(new List<int> { 7182 }); |
|||
|
|||
var nt2 = group.Notifications.FirstOrDefault(n => n.Name.Equals(NotificationsTestsNames.Test2)); |
|||
nt2.WithProviders(WxPusherNotificationPublishProvider.ProviderName) |
|||
.WithContentType(MessageContentType.Html) |
|||
.WithTopics(new List<int> { 7182 }); |
|||
|
|||
var nt3 = group.Notifications.FirstOrDefault(n => n.Name.Equals(NotificationsTestsNames.Test3)); |
|||
nt3.WithProviders(WxPusherNotificationPublishProvider.ProviderName) |
|||
.WithContentType(MessageContentType.Markdown) |
|||
.WithTopics(new List<int> { 7182 }); |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
global using Xunit; |
|||
global using Shouldly; |
|||
Loading…
Reference in new issue