Browse Source

feat(notifications): add webhook notification

- add webhook notification package
- add wecom webhook resolver
pull/1346/head
colin 4 months ago
parent
commit
c834ea543e
  1. 46
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs
  2. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xml
  3. 30
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xsd
  4. 25
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN.Abp.Notifications.Webhook.WeChat.Work.csproj
  5. 18
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkModule.cs
  6. 15
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkOptions.cs
  7. 74
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/WeChatWorkWebhookNotificationContributor.cs
  8. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xml
  9. 30
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xsd
  10. 21
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN.Abp.Notifications.Webhook.csproj
  11. 18
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookModule.cs
  12. 11
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookOptions.cs
  13. 9
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContext.cs
  14. 8
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContributor.cs
  15. 18
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationContext.cs
  16. 16
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationData.cs
  17. 49
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationPublishProvider.cs

46
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs

@ -55,40 +55,13 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider
}
var notificationData = await NotificationDataSerializer.ToStandard(notification.Data);
var toTag = notification.Data.GetTagOrNull() ?? notificationDefine?.GetTagOrNull();
if (!toTag.IsNullOrWhiteSpace())
{
// 指定发送标签
await PublishToAgentAsync(
agentId,
notification,
notificationData.Title,
notificationData.Message,
notificationData.Description,
toTag: toTag,
cancellationToken: cancellationToken);
return;
}
var toParty = notification.Data.GetPartyOrNull() ?? notificationDefine?.GetPartyOrNull();
if (!toParty.IsNullOrWhiteSpace())
{
// 指定发送部门
await PublishToAgentAsync(
agentId,
notification,
notificationData.Title,
notificationData.Message,
notificationData.Description,
toParty: toParty,
cancellationToken: cancellationToken);
return;
}
var toUsers = await WeChatWorkInternalUserFinder.FindUserIdentifierListAsync(identifiers.Select(id => id.UserId));
var findUserList = await WeChatWorkInternalUserFinder
.FindUserIdentifierListAsync(identifiers.Select(id => id.UserId));
if (findUserList.Count == 0)
if (toUsers.IsNullOrEmpty() && toTag.IsNullOrWhiteSpace() && toParty.IsNullOrWhiteSpace())
{
Logger.LogWarning("Unable to send work weixin messages because findUserList is empty.");
// touser、toparty、totag不能同时为空:https://developer.work.weixin.qq.com/document/path/90236
Logger.LogWarning("Unable to send work weixin messages because The recipient/department/label cannot be empty simultaneously.");
return;
}
@ -99,7 +72,9 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider
notificationData.Title,
notificationData.Message,
notificationData.Description,
toUser: findUserList.JoinAsString("|"),
toTag: toTag,
toParty: toParty,
toUser: toUsers.JoinAsString("|"),
cancellationToken: cancellationToken);
}
@ -137,13 +112,6 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider
return;
}
if (toUser.IsNullOrWhiteSpace() && toTag.IsNullOrWhiteSpace() && toParty.IsNullOrWhiteSpace())
{
// touser、toparty、totag不能同时为空:https://developer.work.weixin.qq.com/document/path/90236
Logger.LogWarning("Unable to send work weixin messages because The recipient/department/label cannot be empty simultaneously.");
return;
}
message.ToUser = toUser;
message.ToTag = toTag;
message.ToParty = toParty;

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xsd

@ -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>

25
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN.Abp.Notifications.Webhook.WeChat.Work.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.Notifications.Webhook.WeChat.Work</AssemblyName>
<PackageId>LINGYUN.Abp.Notifications.Webhook.WeChat.Work</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommonMark.NET" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Webhook\LINGYUN.Abp.Notifications.Webhook.csproj" />
</ItemGroup>
</Project>

18
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkModule.cs

@ -0,0 +1,18 @@
using LINGYUN.Abp.WeChat.Work;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work;
[DependsOn(
typeof(AbpNotificationsWebhookModule),
typeof(AbpWeChatWorkModule))]
public class AbpNotificationsWebhookWeChatWorkModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpNotificationsWebhookOptions>(options =>
{
options.Contributors.Add(new WeChatWorkWebhookNotificationContributor());
});
}
}

15
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkOptions.cs

@ -0,0 +1,15 @@
namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work;
public class AbpNotificationsWebhookWeChatWorkOptions
{
/// <summary>
/// 发送Markdown类型通知时是否使用MarkdownV2格式通知, 默认: true
/// </summary>
/// <remarks>
/// 详见: https://developer.work.weixin.qq.com/document/path/99110#markdown-v2%E7%B1%BB%E5%9E%8B
/// </remarks>
public bool UseMarkdownV2 { get; set; }
public AbpNotificationsWebhookWeChatWorkOptions()
{
UseMarkdownV2 = true;
}
}

74
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/WeChatWorkWebhookNotificationContributor.cs

@ -0,0 +1,74 @@
using LINGYUN.Abp.WeChat.Work.Messages;
using LINGYUN.Abp.WeChat.Work.Messages.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work;
public class WeChatWorkWebhookNotificationContributor : IWebhookNotificationContributor
{
public string Name => "WeChat.Work";
public async virtual Task ContributeAsync(IWebhookNotificationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpNotificationsWebhookWeChatWorkOptions>>().Value;
var notificationDataSerializer = context.ServiceProvider.GetRequiredService<INotificationDataSerializer>();
var data = await notificationDataSerializer.ToStandard(context.Notification.Data);
var notificationContent = data.Message;
try
{
if (context.Notification.ContentType == NotificationContentType.Html)
{
notificationContent = CommonMark.CommonMarkConverter.Convert(notificationContent);
}
WeChatWorkWebhookMessage message;
switch (context.Notification.ContentType)
{
case NotificationContentType.Text:
message = new WeChatWorkWebhookTextMessage(
new WebhookTextMessage(notificationContent));
break;
case NotificationContentType.Html:
case NotificationContentType.Markdown:
if (options.UseMarkdownV2)
{
message = new WeChatWorkWebhookMarkdownV2Message(
new WebhookMarkdownV2Message(notificationContent));
}
else
{
message = new WeChatWorkWebhookMarkdownMessage(
new WebhookMarkdownMessage(notificationContent));
}
break;
default:
return;
}
if (message == null)
{
context.ServiceProvider
.GetService<ILogger<WeChatWorkWebhookNotificationContributor>>()
?.LogWarning("Unable to send work weixin messages because WeChatWorkMessage is null.");
return;
}
context.Webhook = new WebhookNotificationData(
context.Notification.Name,
message);
context.Handled = true;
}
catch (Exception ex)
{
context.ServiceProvider
.GetService<ILogger<WeChatWorkWebhookNotificationContributor>>()
?.LogWarning("Failed to parse the content of the Webhook message: {message}", ex.Message);
}
}
}

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xsd

@ -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>

21
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN.Abp.Notifications.Webhook.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.Notifications.Webhook</AssemblyName>
<PackageId>LINGYUN.Abp.Notifications.Webhook</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\webhooks\LINGYUN.Abp.Webhooks\LINGYUN.Abp.Webhooks.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" />
</ItemGroup>
</Project>

18
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookModule.cs

@ -0,0 +1,18 @@
using LINGYUN.Abp.Webhooks;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Notifications.Webhook;
[DependsOn(
typeof(AbpNotificationsCoreModule),
typeof(AbpWebhooksModule))]
public class AbpNotificationsWebhookModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpNotificationsPublishOptions>(options =>
{
options.PublishProviders.Add<WebhookNotificationPublishProvider>();
});
}
}

11
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookOptions.cs

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace LINGYUN.Abp.Notifications.Webhook;
public class AbpNotificationsWebhookOptions
{
public IList<IWebhookNotificationContributor> Contributors { get; }
public AbpNotificationsWebhookOptions()
{
Contributors = new List<IWebhookNotificationContributor>();
}
}

9
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContext.cs

@ -0,0 +1,9 @@
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Notifications.Webhook;
public interface IWebhookNotificationContext : IServiceProviderAccessor
{
WebhookNotificationData Webhook { get; set; }
NotificationInfo Notification { get; }
bool Handled { get; set; }
}

8
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContributor.cs

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications.Webhook;
public interface IWebhookNotificationContributor
{
string Name { get; }
Task ContributeAsync(IWebhookNotificationContext context);
}

18
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationContext.cs

@ -0,0 +1,18 @@
using System;
namespace LINGYUN.Abp.Notifications.Webhook;
public class WebhookNotificationContext : IWebhookNotificationContext
{
public IServiceProvider ServiceProvider { get; }
public NotificationInfo Notification { get; }
public WebhookNotificationData Webhook { get; set; }
public bool Handled { get; set; }
public WebhookNotificationContext(IServiceProvider ServiceProvider, NotificationInfo notification)
{
Notification = notification;
}
public bool HasResolved()
{
return Handled || Webhook != null;
}
}

16
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationData.cs

@ -0,0 +1,16 @@
using LINGYUN.Abp.Webhooks;
using Volo.Abp;
namespace LINGYUN.Abp.Notifications.Webhook;
public class WebhookNotificationData
{
public string WebhookName { get; }
public object Data { get; }
public bool SendExactSameData { get; set; }
public WebhookHeader Headers { get; set; }
public WebhookNotificationData(string webhookName, object data)
{
WebhookName = Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName));
Data = Check.NotNull(data, nameof(data));
}
}

49
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationPublishProvider.cs

@ -0,0 +1,49 @@
using LINGYUN.Abp.Webhooks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications.Webhook;
public class WebhookNotificationPublishProvider : NotificationPublishProvider
{
public const string ProviderName = "Webhook";
public override string Name => ProviderName;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly AbpNotificationsWebhookOptions _options;
private readonly IWebhookPublisher _webhookPublisher;
public WebhookNotificationPublishProvider(
IServiceScopeFactory serviceScopeFactory,
IWebhookPublisher webhookPublisher,
IOptions<AbpNotificationsWebhookOptions> options)
{
_serviceScopeFactory = serviceScopeFactory;
_webhookPublisher = webhookPublisher;
_options = options.Value;
}
protected override async Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers, CancellationToken cancellationToken = default)
{
using var scope = _serviceScopeFactory.CreateScope();
foreach (var contributor in _options.Contributors)
{
var context = new WebhookNotificationContext(scope.ServiceProvider, notification);
await contributor.ContributeAsync(context);
if (context.HasResolved())
{
await _webhookPublisher.PublishAsync(
context.Webhook.WebhookName,
context.Webhook.Data,
notification.TenantId,
context.Webhook.SendExactSameData,
context.Webhook.Headers);
}
}
}
}
Loading…
Cancel
Save