Browse Source

feature: Integrated PushPlus.

pull/658/head
cKey 3 years ago
parent
commit
c2f5da5fbd
  1. 38
      aspnet-core/LINGYUN.MicroService.Common.sln
  2. 3
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml
  3. 20
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj
  4. 36
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/NotificationDefinitionExtensions.cs
  5. 18
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/AbpNotificationsPushPlusModule.cs
  6. 88
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs
  7. 16
      aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/README.md
  8. 3
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xml
  9. 30
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xsd
  10. 29
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj
  11. 53
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs
  12. 27
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs
  13. 16
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelTypeExtensions.cs
  14. 62
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/IPushPlusWebhookProvider.cs
  15. 44
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhook.cs
  16. 125
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookProvider.cs
  17. 20
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookType.cs
  18. 108
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/WebhookHttpClientExtensions.cs
  19. 168
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureDefinitionProvider.cs
  20. 103
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureNames.cs
  21. 8
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/PushPlusResource.cs
  22. 47
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/en.json
  23. 47
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/zh-Hans.json
  24. 28
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageProvider.cs
  25. 52
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageSender.cs
  26. 88
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/MessageHttpClientExtensions.cs
  27. 44
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessage.cs
  28. 67
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageProvider.cs
  29. 214
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSender.cs
  30. 21
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageStatus.cs
  31. 35
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs
  32. 13
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageType.cs
  33. 27
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/SendPushPlusMessageResult.cs
  34. 38
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusPagedResponse.cs
  35. 14
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusRequestException.cs
  36. 58
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusResponse.cs
  37. 38
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/IPushPlusChannelProvider.cs
  38. 35
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannel.cs
  39. 83
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelProvider.cs
  40. 15
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs
  41. 27
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs
  42. 65
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/SettingHttpClientExtensions.cs
  43. 42
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingDefinitionProvider.cs
  44. 15
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingNames.cs
  45. 16
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/IPushPlusTokenProvider.cs
  46. 28
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusToken.cs
  47. 26
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenCacheItem.cs
  48. 104
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenProvider.cs
  49. 29
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/TokenHttpClientExtensions.cs
  50. 100
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/IPushPlusTopicProvider.cs
  51. 30
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopic.cs
  52. 19
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicForMe.cs
  53. 34
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProfile.cs
  54. 196
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProvider.cs
  55. 19
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCode.cs
  56. 12
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCodeType.cs
  57. 12
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicType.cs
  58. 48
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicUser.cs
  59. 177
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/TopicHttpClientExtensions.cs
  60. 28
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/IPushPlusUserProvider.cs
  61. 17
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserEmailStatus.cs
  62. 12
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserFollowStatus.cs
  63. 20
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserLimitTime.cs
  64. 12
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserPhoneBindStatus.cs
  65. 69
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProfile.cs
  66. 74
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProvider.cs
  67. 17
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSendLimit.cs
  68. 16
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSex.cs
  69. 56
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/UserHttpClientExtensions.cs
  70. 19
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/Microsoft/Extensions/DependencyInjection/IServiceConnectionExtensions.cs
  71. 42
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/README.md
  72. 10
      aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/System/Net/Http/IHttpClientFactoryExtensions.cs
  73. 18
      aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj
  74. 7
      aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestBase.cs
  75. 24
      aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestModule.cs
  76. 40
      aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSenderTests.cs
  77. 7
      aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs

38
aspnet-core/LINGYUN.MicroService.Common.sln

@ -238,6 +238,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Organi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.DistributedLocking.Dapr", "modules\dapr\LINGYUN.Abp.DistributedLocking.Dapr\LINGYUN.Abp.DistributedLocking.Dapr.csproj", "{C71F6273-BCDE-4A63-A0CF-EAFD1D924DA0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pushplus", "pushplus", "{0F5A2591-CE08-4184-A5F3-89F6FB3B2B10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.PushPlus", "modules\pushplus\LINGYUN.Abp.PushPlus\LINGYUN.Abp.PushPlus.csproj", "{5515C7CA-B512-4E36-A202-49A0158A0E74}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Notifications.PushPlus", "modules\pushplus\LINGYUN.Abp.Notifications.PushPlus\LINGYUN.Abp.Notifications.PushPlus.csproj", "{EBA67EAD-4958-46E3-9E0C-8186394D083F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.Emailing", "modules\common\LINGYUN.Abp.Notifications.Emailing\LINGYUN.Abp.Notifications.Emailing.csproj", "{25891EE2-3166-420F-8408-E458030C4643}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.Common", "modules\common\LINGYUN.Abp.Notifications.Common\LINGYUN.Abp.Notifications.Common.csproj", "{F051C960-AA61-4283-A088-611C0B96C953}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.PushPlus.Tests", "tests\LINGYUN.Abp.PushPlus.Tests\LINGYUN.Abp.PushPlus.Tests.csproj", "{1435711B-D796-42AB-B567-0BB23F02EE08}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -608,6 +620,26 @@ Global
{C71F6273-BCDE-4A63-A0CF-EAFD1D924DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C71F6273-BCDE-4A63-A0CF-EAFD1D924DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C71F6273-BCDE-4A63-A0CF-EAFD1D924DA0}.Release|Any CPU.Build.0 = Release|Any CPU
{5515C7CA-B512-4E36-A202-49A0158A0E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5515C7CA-B512-4E36-A202-49A0158A0E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5515C7CA-B512-4E36-A202-49A0158A0E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5515C7CA-B512-4E36-A202-49A0158A0E74}.Release|Any CPU.Build.0 = Release|Any CPU
{EBA67EAD-4958-46E3-9E0C-8186394D083F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBA67EAD-4958-46E3-9E0C-8186394D083F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA67EAD-4958-46E3-9E0C-8186394D083F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA67EAD-4958-46E3-9E0C-8186394D083F}.Release|Any CPU.Build.0 = Release|Any CPU
{25891EE2-3166-420F-8408-E458030C4643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25891EE2-3166-420F-8408-E458030C4643}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25891EE2-3166-420F-8408-E458030C4643}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25891EE2-3166-420F-8408-E458030C4643}.Release|Any CPU.Build.0 = Release|Any CPU
{F051C960-AA61-4283-A088-611C0B96C953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F051C960-AA61-4283-A088-611C0B96C953}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F051C960-AA61-4283-A088-611C0B96C953}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F051C960-AA61-4283-A088-611C0B96C953}.Release|Any CPU.Build.0 = Release|Any CPU
{1435711B-D796-42AB-B567-0BB23F02EE08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1435711B-D796-42AB-B567-0BB23F02EE08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1435711B-D796-42AB-B567-0BB23F02EE08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1435711B-D796-42AB-B567-0BB23F02EE08}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -726,6 +758,12 @@ Global
{D9339CBB-45B9-4701-B2AC-2A75FF20D77B} = {3971AD93-BF97-4E05-972D-CB5EB9F6CB88}
{474AA48F-65F9-436B-A0B1-1E95BD16CA8D} = {3971AD93-BF97-4E05-972D-CB5EB9F6CB88}
{C71F6273-BCDE-4A63-A0CF-EAFD1D924DA0} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50}
{0F5A2591-CE08-4184-A5F3-89F6FB3B2B10} = {02EA4E78-5891-43BC-944F-3E52FEE032E4}
{5515C7CA-B512-4E36-A202-49A0158A0E74} = {0F5A2591-CE08-4184-A5F3-89F6FB3B2B10}
{EBA67EAD-4958-46E3-9E0C-8186394D083F} = {0F5A2591-CE08-4184-A5F3-89F6FB3B2B10}
{25891EE2-3166-420F-8408-E458030C4643} = {B91F26C5-B148-4094-B5F1-71E5F945DBED}
{F051C960-AA61-4283-A088-611C0B96C953} = {B91F26C5-B148-4094-B5F1-71E5F945DBED}
{1435711B-D796-42AB-B567-0BB23F02EE08} = {B86C21A4-73B7-471E-B73A-B4B905EC9435}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8}

3
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml

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

20
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj

@ -0,0 +1,20 @@
<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.Sms" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.PushPlus\LINGYUN.Abp.PushPlus.csproj" />
</ItemGroup>
</Project>

36
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/NotificationDefinitionExtensions.cs

@ -0,0 +1,36 @@
namespace LINGYUN.Abp.Notifications;
public static class NotificationDefinitionExtensions
{
/// <summary>
/// 消息群发群组编码
/// see: https://www.pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3
/// </summary>
/// <param name="notification">群组编码</param>
/// <param name="topic"></param>
/// <returns>
/// <see cref="NotificationDefinition"/>
/// </returns>
public static NotificationDefinition WithTopic(
this NotificationDefinition notification,
string topic)
{
return notification.WithProperty("topic", topic);
}
/// <summary>
/// 获取消息群发群组编码
/// </summary>
/// <param name="notification"></param>
/// <returns>
/// 通知定义的群组编码,未定义返回null
/// </returns>
public static string GetTopicOrNull(
this NotificationDefinition notification)
{
if (notification.Properties.TryGetValue("topic", out var topicDefine) == true)
{
return topicDefine.ToString();
}
return null;
}
}

18
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/AbpNotificationsPushPlusModule.cs

@ -0,0 +1,18 @@
using LINGYUN.Abp.PushPlus;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Notifications.PushPlus;
[DependsOn(
typeof(AbpNotificationModule),
typeof(AbpPushPlusModule))]
public class AbpNotificationsPushPlusModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpNotificationOptions>(options =>
{
options.PublishProviders.Add<PushPlusNotificationPublishProvider>();
});
}
}

88
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs

@ -0,0 +1,88 @@
using LINGYUN.Abp.PushPlus.Message;
using LINGYUN.Abp.RealTime.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Notifications.PushPlus;
public class PushPlusNotificationPublishProvider : NotificationPublishProvider
{
public const string ProviderName = "PushPlus";
public override string Name => ProviderName;
protected IPushPlusMessageSender PushPlusMessageSender { get; }
protected IStringLocalizerFactory LocalizerFactory { get; }
protected AbpLocalizationOptions LocalizationOptions { get; }
protected INotificationDefinitionManager NotificationDefinitionManager { get; }
public PushPlusNotificationPublishProvider(
IPushPlusMessageSender pushPlusMessageSender,
IStringLocalizerFactory localizerFactory,
IOptions<AbpLocalizationOptions> localizationOptions,
INotificationDefinitionManager notificationDefinitionManager)
{
PushPlusMessageSender = pushPlusMessageSender;
LocalizerFactory = localizerFactory;
LocalizationOptions = localizationOptions.Value;
NotificationDefinitionManager = notificationDefinitionManager;
}
protected async override Task PublishAsync(
NotificationInfo notification,
IEnumerable<UserIdentifier> identifiers,
CancellationToken cancellationToken = default)
{
var topic = "";
var notificationDefine = NotificationDefinitionManager.GetOrNull(notification.Name);
var topicDefine = notificationDefine?.GetTopicOrNull();
if (!topicDefine.IsNullOrWhiteSpace())
{
topic = topicDefine;
}
if (!notification.Data.NeedLocalizer())
{
var title = notification.Data.TryGetData("title").ToString();
var message = notification.Data.TryGetData("message").ToString();
await PushPlusMessageSender.SendAsync(
title,
message,
topic,
cancellationToken: cancellationToken);
}
else
{
var titleInfo = notification.Data.TryGetData("title").As<LocalizableStringInfo>();
var titleResource = GetResource(titleInfo.ResourceName);
var title = LocalizerFactory.Create(titleResource.ResourceType)[titleInfo.Name, titleInfo.Values].Value;
var messageInfo = notification.Data.TryGetData("message").As<LocalizableStringInfo>();
var messageResource = GetResource(messageInfo.ResourceName);
var message = LocalizerFactory.Create(messageResource.ResourceType)[messageInfo.Name, messageInfo.Values].Value;
await PushPlusMessageSender.SendAsync(
title,
message,
topic,
cancellationToken: cancellationToken);
}
}
private LocalizationResource GetResource(string resourceName)
{
return LocalizationOptions.Resources.Values
.First(x => x.ResourceName.Equals(resourceName));
}
}

16
aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/README.md

@ -0,0 +1,16 @@
# LINGYUN.Abp.Notifications.PushPlus
通知模块的PushPlus实现
使应用可通过PushPlus发布实时通知
## 模块引用
```csharp
[DependsOn(typeof(AbpNotificationsPushPlusModule))]
public class YouProjectModule : AbpModule
{
// other
}
```

3
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/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/pushplus/LINGYUN.Abp.PushPlus/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>

29
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\PushPlus\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LINGYUN\Abp\PushPlus\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Json" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Http" Version="$(MicrosoftPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Features.LimitValidation\LINGYUN.Abp.Features.LimitValidation.csproj" />
</ItemGroup>
</Project>

53
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs

@ -0,0 +1,53 @@
using LINGYUN.Abp.Features.LimitValidation;
using LINGYUN.Abp.PushPlus.Channel.Webhook;
using LINGYUN.Abp.PushPlus.Message;
using LINGYUN.Abp.PushPlus.Setting;
using LINGYUN.Abp.PushPlus.Token;
using LINGYUN.Abp.PushPlus.Topic;
using LINGYUN.Abp.PushPlus.User;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Json;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.PushPlus;
[DependsOn(
typeof(AbpJsonModule),
typeof(AbpSettingsModule),
typeof(AbpCachingModule),
typeof(AbpFeaturesLimitValidationModule))]
public class AbpPushPlusModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddPushPlusClient();
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
options.UnsupportedTypes.TryAdd<PushPlusResponse<int>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<string>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<object>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusWebhook>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusMessage>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<SendPushPlusMessageResult>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusMessage>>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusChannel>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusToken>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicForMe>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicProfile>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusTopicQrCode>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusTopicUser>>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusPagedResponse<PushPlusTopic>>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusUserLimitTime>>();
options.UnsupportedTypes.TryAdd<PushPlusResponse<PushPlusUserProfile>>();
});
}
}

27
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs

@ -0,0 +1,27 @@
namespace LINGYUN.Abp.PushPlus.Channel;
/// <summary>
/// 发送渠道(channel)枚举
/// </summary>
public enum PushPlusChannelType
{
/// <summary>
/// 微信公众号
/// </summary>
WeChat = 0,
/// <summary>
/// 第三方webhook
/// </summary>
Webhook = 1,
/// <summary>
/// 企业微信应用
/// </summary>
WeWork = 2,
/// <summary>
/// 邮箱
/// </summary>
Email = 3,
/// <summary>
/// 短信
/// </summary>
Sms = 4
}

16
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelTypeExtensions.cs

@ -0,0 +1,16 @@
namespace LINGYUN.Abp.PushPlus.Channel;
public static class PushPlusChannelTypeExtensions
{
public static string GetChannelName(this PushPlusChannelType channelType)
{
return channelType switch
{
PushPlusChannelType.WeChat => "wechat",
PushPlusChannelType.WeWork => "cp",
PushPlusChannelType.Webhook => "webhook",
PushPlusChannelType.Email => "mail",
PushPlusChannelType.Sms => "sms",
_ => "wechat",
};
}
}

62
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/IPushPlusWebhookProvider.cs

@ -0,0 +1,62 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Channel.Webhook;
/// <summary>
/// Webhook配置接口
/// </summary>
public interface IPushPlusWebhookProvider
{
/// <summary>
/// 获取webhook列表
/// </summary>
/// <param name="current">当前所在分页数</param>
/// <param name="pageSize">每页大小,最大值为50</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusPagedResponse<PushPlusWebhook>> GetWebhookListAsync(
int current = 1,
int pageSize = 20,
CancellationToken cancellationToken = default);
/// <summary>
/// webhook详情
/// </summary>
/// <param name="webhookId">webhook编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusWebhook> GetWebhookAsync(
int webhookId,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增webhook
/// </summary>
/// <param name="webhookCode">webhook编码</param>
/// <param name="webhookName">webhook名称</param>
/// <param name="webhookType">webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱</param>
/// <param name="webhookUrl">调用的url地址</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<int> CreateWebhookAsync(
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default);
/// <summary>
/// 修改webhook配置
/// </summary>
/// <param name="id">webhook编号</param>
/// <param name="webhookCode">webhook编码</param>
/// <param name="webhookName">webhook名称</param>
/// <param name="webhookType">webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱</param>
/// <param name="webhookUrl">调用的url地址</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> UpdateWebhookAsync(
int id,
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default);
}

44
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhook.cs

@ -0,0 +1,44 @@
using Newtonsoft.Json;
using System;
namespace LINGYUN.Abp.PushPlus.Channel.Webhook;
/// <summary>
/// webhook
/// </summary>
public class PushPlusWebhook
{
/// <summary>
/// webhook编号
/// </summary>
[JsonProperty("id")]
public int Id { get; set; }
/// <summary>
/// webhook编码
/// </summary>
[JsonProperty("webhookCode")]
public string WebhookCode { get; set; }
/// <summary>
/// webhook名称
/// </summary>
[JsonProperty("webhookName")]
public string WebhookName { get; set; }
/// <summary>
/// webhook类型;
/// 1-企业微信,
/// 2-钉钉,
/// 3-飞书,
/// 4-server酱
/// </summary>
[JsonProperty("webhookType")]
public PushPlusWebhookType WebhookType { get; set; }
/// <summary>
/// 调用的url地址
/// </summary>
[JsonProperty("webhookUrl")]
public string WebhookUrl { get; set; }
/// <summary>
/// 创建日期
/// </summary>
[JsonProperty("createTime")]
public DateTime CreateTime { get; set; }
}

125
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookProvider.cs

@ -0,0 +1,125 @@
using LINGYUN.Abp.PushPlus.Token;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace LINGYUN.Abp.PushPlus.Channel.Webhook;
public class PushPlusWebhookProvider : IPushPlusWebhookProvider, ITransientDependency
{
protected ILogger<PushPlusWebhookProvider> Logger { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IPushPlusTokenProvider PushPlusTokenProvider { get; }
public PushPlusWebhookProvider(
ILogger<PushPlusWebhookProvider> logger,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IPushPlusTokenProvider pushPlusTokenProvider)
{
Logger = logger;
JsonSerializer = jsonSerializer;
HttpClientFactory = httpClientFactory;
PushPlusTokenProvider = pushPlusTokenProvider;
}
public async virtual Task<int> CreateWebhookAsync(
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default)
{
Check.NotNullOrWhiteSpace(webhookCode, nameof(webhookCode));
Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName));
Check.NotNullOrWhiteSpace(webhookUrl, nameof(webhookUrl));
Check.NotNull(webhookType, nameof(webhookType));
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetCreateWebhookContentAsync(
token.AccessKey,
webhookCode,
webhookName,
webhookType,
webhookUrl,
cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<int>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusWebhook> GetWebhookAsync(
int webhookId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetWebhookContentAsync(
token.AccessKey,
webhookId,
cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<PushPlusWebhook>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusPagedResponse<PushPlusWebhook>> GetWebhookListAsync(
int current = 1,
int pageSize = 20,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetWebhookListContentAsync(
token.AccessKey,
current,
pageSize,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusWebhook>>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<string> UpdateWebhookAsync(
int id,
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default)
{
Check.NotNullOrWhiteSpace(webhookCode, nameof(webhookCode));
Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName));
Check.NotNullOrWhiteSpace(webhookUrl, nameof(webhookUrl));
Check.NotNull(webhookType, nameof(webhookType));
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetUpdateWebhookContentAsync(
token.AccessKey,
id,
webhookCode,
webhookName,
webhookType,
webhookUrl,
cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<string>>(content);
return pushPlusResponse.GetData();
}
}

20
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookType.cs

@ -0,0 +1,20 @@
namespace LINGYUN.Abp.PushPlus.Channel.Webhook;
public enum PushPlusWebhookType
{
/// <summary>
/// 企业微信
/// </summary>
WeWork = 1,
/// <summary>
/// 钉钉
/// </summary>
DingTalk = 2,
/// <summary>
/// 飞书
/// </summary>
FeiShu = 3,
/// <summary>
/// Server酱
/// </summary>
SctFtqq = 4,
}

108
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/WebhookHttpClientExtensions.cs

@ -0,0 +1,108 @@
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Channel.Webhook;
internal static class WebhookHttpClientExtensions
{
private const string _getWebhookListTemplate = "{\"current\":$current,\"pageSize\":$pageSize}";
public async static Task<string> GetWebhookListContentAsync(
this HttpClient httpClient,
string accessKey,
int current,
int pageSize,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/webhook/list");
var requestBody = _getWebhookListTemplate
.Replace("$current", current.ToString())
.Replace("$pageSize", pageSize.ToString());
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetWebhookContentAsync(
this HttpClient httpClient,
string accessKey,
int webhookId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/webhook/detail?webhookId={webhookId}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
private const string _createWebhookTemplate = "{\"webhookCode\":\"$webhookCode\",\"webhookName\":\"$webhookName\",\"webhookType\":$webhookType,\"webhookUrl\":\"$webhookUrl\"}";
public async static Task<string> GetCreateWebhookContentAsync(
this HttpClient httpClient,
string accessKey,
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/webhook/add");
var requestBody = _createWebhookTemplate
.Replace("$webhookCode", webhookCode)
.Replace("$webhookName", webhookName)
.Replace("$webhookType", ((int)webhookType).ToString())
.Replace("$webhookUrl", webhookUrl);
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
private const string _updateWebhookTemplate = "{\"id\":$id,\"webhookCode\":\"$webhookCode\",\"webhookName\":\"$webhookName\",\"webhookType\":$webhookType,\"webhookUrl\":\"$webhookUrl\"}";
public async static Task<string> GetUpdateWebhookContentAsync(
this HttpClient httpClient,
string accessKey,
int id,
string webhookCode,
string webhookName,
PushPlusWebhookType webhookType,
string webhookUrl,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/topic/edit");
var requestBody = _updateWebhookTemplate
.Replace("$id", id.ToString())
.Replace("$webhookCode", webhookCode)
.Replace("$webhookName", webhookName)
.Replace("$webhookType", ((int)webhookType).ToString())
.Replace("$webhookUrl", webhookUrl);
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

168
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureDefinitionProvider.cs

@ -0,0 +1,168 @@
using LINGYUN.Abp.PushPlus.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace LINGYUN.Abp.PushPlus.Features;
public class PushPlusFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.AddGroup(
name: PushPlusFeatureNames.GroupName,
displayName: L("Features:PushPlus"));
group.AddFeature(
name: PushPlusFeatureNames.Message.Enable,
defaultValue: "true",
displayName: L("Features:MessageEnable"),
description: L("Features:MessageEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
CreateWeChatChannelFeature(group);
CreateWeWorkChannelFeature(group);
CreateWebhookChannelFeature(group);
CreateEmailChannelFeature(group);
CreateSmsChannelFeature(group);
}
private static void CreateWeChatChannelFeature(
FeatureGroupDefinition group)
{
var weChatChannel = group.AddFeature(
name: PushPlusFeatureNames.Channel.WeChat.GroupName,
displayName: L("Features:Channel.WeChat"),
description: L("Features:Channel.WeChat"));
weChatChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeChat.Enable,
defaultValue: "true",
displayName: L("Features:Channel.WeChat.Enable"),
description: L("Features:Channel.WeChat.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
weChatChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeChat.SendLimit,
defaultValue: "200",
displayName: L("Features:Channel.WeChat.SendLimit"),
description: L("Features:Channel.WeChat.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000)));
weChatChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeChat.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Channel.WeChat.SendLimitInterval"),
description: L("Features:Channel.WeChat.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static void CreateWeWorkChannelFeature(
FeatureGroupDefinition group)
{
var weWorkChannel = group.AddFeature(
name: PushPlusFeatureNames.Channel.WeWork.GroupName,
displayName: L("Features:Channel.WeWork"),
description: L("Features:Channel.WeWork"));
weWorkChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeWork.Enable,
defaultValue: "true",
displayName: L("Features:Channel.WeWork.Enable"),
description: L("Features:Channel.WeWork.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
weWorkChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeWork.SendLimit,
defaultValue: "200",
displayName: L("Features:Channel.WeWork.SendLimit"),
description: L("Features:Channel.WeWork.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 10000)));
weWorkChannel.CreateChild(
name: PushPlusFeatureNames.Channel.WeWork.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Channel.WeWork.SendLimitInterval"),
description: L("Features:Channel.WeWork.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static void CreateWebhookChannelFeature(
FeatureGroupDefinition group)
{
var webhookChannel = group.AddFeature(
name: PushPlusFeatureNames.Channel.Webhook.GroupName,
displayName: L("Features:Channel.Webhook"),
description: L("Features:Channel.Webhook"));
webhookChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Webhook.Enable,
defaultValue: "true",
displayName: L("Features:Channel.Webhook.Enable"),
description: L("Features:Channel.Webhook.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
webhookChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Webhook.SendLimit,
defaultValue: "200",
displayName: L("Features:Channel.Webhook.SendLimit"),
description: L("Features:Channel.Webhook.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 10000)));
webhookChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Webhook.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Channel.Webhook.SendLimitInterval"),
description: L("Features:Channel.Webhook.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static void CreateEmailChannelFeature(
FeatureGroupDefinition group)
{
var emailChannel = group.AddFeature(
name: PushPlusFeatureNames.Channel.Email.GroupName,
displayName: L("Features:Channel.Email"),
description: L("Features:Channel.Email"));
emailChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Email.Enable,
defaultValue: "true",
displayName: L("Features:Channel.Email.Enable"),
description: L("Features:Channel.Email.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
emailChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Email.SendLimit,
defaultValue: "200",
displayName: L("Features:Channel.Email.SendLimit"),
description: L("Features:Channel.Email.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000)));
emailChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Email.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Channel.Email.SendLimitInterval"),
description: L("Features:Channel.Email.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static void CreateSmsChannelFeature(
FeatureGroupDefinition group)
{
var smsChannel = group.AddFeature(
name: PushPlusFeatureNames.Channel.Sms.GroupName,
displayName: L("Features:Channel.Sms"),
description: L("Features:Channel.Sms"));
smsChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Sms.Enable,
defaultValue: "false",
displayName: L("Features:Channel.Sms.Enable"),
description: L("Features:Channel.Sms.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
smsChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Sms.SendLimit,
defaultValue: "200",
displayName: L("Features:Channel.Sms.SendLimit"),
description: L("Features:Channel.Sms.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000)));
smsChannel.CreateChild(
name: PushPlusFeatureNames.Channel.Sms.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Channel.Sms.SendLimitInterval"),
description: L("Features:Channel.Sms.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<PushPlusResource>(name);
}
}

103
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureNames.cs

@ -0,0 +1,103 @@
namespace LINGYUN.Abp.PushPlus.Features;
public static class PushPlusFeatureNames
{
public const string GroupName = "PushPlus";
public static class Message
{
public const string GroupName = PushPlusFeatureNames.GroupName + ".Message";
public const string Enable = GroupName + ".Enable";
}
public static class Channel
{
public const string GroupName = PushPlusFeatureNames.GroupName + ".Channel";
public static class WeChat
{
public const string GroupName = Channel.GroupName + ".WeChat";
/// <summary>
/// 启用微信通道
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
public static class WeWork
{
public const string GroupName = Channel.GroupName + ".WeWork";
/// <summary>
/// 启用企业微信通道
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
public static class Webhook
{
public const string GroupName = Channel.GroupName + ".Webhook";
/// <summary>
/// 启用Webhook通道
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
public static class Email
{
public const string GroupName = Channel.GroupName + ".Email";
/// <summary>
/// 启用Email通道
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
public static class Sms
{
public const string GroupName = Channel.GroupName + ".Sms";
/// <summary>
/// 启用Sms通道
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
}
}

8
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/PushPlusResource.cs

@ -0,0 +1,8 @@
using Volo.Abp.Localization;
namespace LINGYUN.Abp.PushPlus.Localization;
[LocalizationResourceName("PushPlus")]
public class PushPlusResource
{
}

47
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/en.json

@ -0,0 +1,47 @@
{
"culture": "en",
"texts": {
"Settings:Security.Token": "User Token",
"Settings:Security.TokenDesc": "It can be obtained after logging in to the public account of PushPlus.",
"Settings:Security.SecretKey": "Secret Key",
"Settings:Security.SecretKeyDesc": "It can be obtained after logging in to the public account of PushPlus.",
"Features:PushPlus": "PushPlus",
"Features:MessageEnable": "Enable PushPlus",
"Features:MessageEnableDesc": "Enable so that the application will have the ability to push messages via PushPlus.",
"Features:Channel.WeChat": "Wechat Channel",
"Features:Channel.WeChat.Enable": "Enable Wechat Channel",
"Features:Channel.WeChat.EnableDesc": "Enable to allow PushPlus to push messages via the wechat official account.",
"Features:Channel.WeChat.SendLimit": "Wechat Channel Limit",
"Features:Channel.WeChat.SendLimitDesc": "Set to limit the amount of wechat channel push.",
"Features:Channel.WeChat.SendLimitInterval": "Wechat Channel Limit Interval",
"Features:Channel.WeChat.SendLimitIntervalDesc": "Set the limit period of wechat channel (time scale: days).",
"Features:Channel.WeWork": "WeWork Channel",
"Features:Channel.WeWork.Enable": "Enable WeWork Channel",
"Features:Channel.WeWork.EnableDesc": "Enable to allow PushPlus to push messages via the enterprise wechat.",
"Features:Channel.WeWork.SendLimit": "WeWork Channel Limit",
"Features:Channel.WeWork.SendLimitDesc": "Set to limit the amount of enterprise wechat channel push.",
"Features:Channel.WeWork.SendLimitInterval": "WeWork Channel Limit Interval",
"Features:Channel.WeWork.SendLimitIntervalDesc": "Set the limit period of enterprise wechat channel (time scale: days).",
"Features:Channel.Webhook": "Webhook Channel",
"Features:Channel.Webhook.Enable": "Enable Webhook Channel",
"Features:Channel.Webhook.EnableDesc": "Enable to allow PushPlus to push messages through third-party webhooks.",
"Features:Channel.Webhook.SendLimit": "Webhook Channel Limit",
"Features:Channel.Webhook.SendLimitDesc": "Set to limit the amount of Webhook channel push.",
"Features:Channel.Webhook.SendLimitInterval": "Webhook Channel Limit Interval",
"Features:Channel.Webhook.SendLimitIntervalDesc": "Set the Webhook channel limit period (time scale: days).",
"Features:Channel.Email": "Email Channel",
"Features:Channel.Email.Enable": "Enable Email Channel",
"Features:Channel.Email.EnableDesc": "Enable to allow PushPlus to push messages via Email.",
"Features:Channel.Email.SendLimit": "Email Channel Limit",
"Features:Channel.Email.SendLimitDesc": "Set to limit the amount of Email channel push.",
"Features:Channel.Email.SendLimitInterval": "Email Channel Limit Interval",
"Features:Channel.Email.SendLimitIntervalDesc": "Set the Email channel limit period (time scale: days).",
"Features:Channel.Sms": "Sms Channel",
"Features:Channel.Sms.Enable": "Enable Sms Channel",
"Features:Channel.Sms.EnableDesc": "Enable to allow PushPlus to push messages via sms.",
"Features:Channel.Sms.SendLimit": "Sms Channel Limit",
"Features:Channel.Sms.SendLimitDesc": "Set to limit the amount of sms channel push.",
"Features:Channel.Sms.SendLimitInterval": "Sms Channel Limit Interval",
"Features:Channel.Sms.SendLimitIntervalDesc": "Set the sms channel limit period (time scale: days)."
}
}

47
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/zh-Hans.json

@ -0,0 +1,47 @@
{
"culture": "zh-Hans",
"texts": {
"Settings:Security.Token": "用户令牌",
"Settings:Security.TokenDesc": "在PushPlus公众号登录后获取.",
"Settings:Security.SecretKey": "用户密钥",
"Settings:Security.SecretKeyDesc": "在PushPlus公众号登录后获取.",
"Features:PushPlus": "PushPlus(推送加)",
"Features:MessageEnable": "启用PushPlus消息推送",
"Features:MessageEnableDesc": "启用以使应用将拥有通过PushPlus推送消息的能力.",
"Features:Channel.WeChat": "微信消息通道",
"Features:Channel.WeChat.Enable": "启用微信消息通道",
"Features:Channel.WeChat.EnableDesc": "启用以允许PushPlus通过微信公众号推送消息.",
"Features:Channel.WeChat.SendLimit": "微信渠道推送量",
"Features:Channel.WeChat.SendLimitDesc": "设置以限制微信渠道推送量.",
"Features:Channel.WeChat.SendLimitInterval": "微信渠道限制周期",
"Features:Channel.WeChat.SendLimitIntervalDesc": "设置微信渠道限制周期(时间刻度: 天).",
"Features:Channel.WeWork": "企业微信消息通道",
"Features:Channel.WeWork.Enable": "启用企业微信消息通道",
"Features:Channel.WeWork.EnableDesc": "启用以允许PushPlus通过企业微信应用推送消息.",
"Features:Channel.WeWork.SendLimit": "企业微信渠道推送量",
"Features:Channel.WeWork.SendLimitDesc": "设置以限制企业微信渠道推送量.",
"Features:Channel.WeWork.SendLimitInterval": "企业微信渠道限制周期",
"Features:Channel.WeWork.SendLimitIntervalDesc": "设置企业微信渠道限制周期(时间刻度: 天).",
"Features:Channel.Webhook": "Webhook消息通道",
"Features:Channel.Webhook.Enable": "启用Webhook消息通道",
"Features:Channel.Webhook.EnableDesc": "启用以允许PushPlus通过第三方Webhook推送消息.",
"Features:Channel.Webhook.SendLimit": "Webhook渠道推送量",
"Features:Channel.Webhook.SendLimitDesc": "设置以限制Webhook渠道推送量.",
"Features:Channel.Webhook.SendLimitInterval": "Webhook渠道限制周期",
"Features:Channel.Webhook.SendLimitIntervalDesc": "设置Webhook渠道限制周期(时间刻度: 天).",
"Features:Channel.Email": "Email消息通道",
"Features:Channel.Email.Enable": "启用Email消息通道",
"Features:Channel.Email.EnableDesc": "启用以允许PushPlus通过Email推送消息.",
"Features:Channel.Email.SendLimit": "Email渠道推送量",
"Features:Channel.Email.SendLimitDesc": "设置以限制Email渠道推送量.",
"Features:Channel.Email.SendLimitInterval": "Email渠道限制周期",
"Features:Channel.Email.SendLimitIntervalDesc": "设置Email渠道限制周期(时间刻度: 天).",
"Features:Channel.Sms": "短信消息通道",
"Features:Channel.Sms.Enable": "启用短信消息通道",
"Features:Channel.Sms.EnableDesc": "启用以允许PushPlus通过短信推送消息.",
"Features:Channel.Sms.SendLimit": "短信渠道推送量",
"Features:Channel.Sms.SendLimitDesc": "设置以限制短信渠道推送量.",
"Features:Channel.Sms.SendLimitInterval": "短信渠道限制周期",
"Features:Channel.Sms.SendLimitIntervalDesc": "设置短信渠道限制周期(时间刻度: 天)."
}
}

28
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageProvider.cs

@ -0,0 +1,28 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Message;
public interface IPushPlusMessageProvider
{
/// <summary>
/// 查询消息发送结果
/// </summary>
/// <param name="shortCode">消息短链码</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<SendPushPlusMessageResult> GetSendResultAsync(
string shortCode,
CancellationToken cancellationToken = default);
/// <summary>
/// 消息列表
/// </summary>
/// <param name="current">当前所在分页数</param>
/// <param name="pageSize">每页大小,最大值为50</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusPagedResponse<PushPlusMessage>> GetMessageListAsync(
int current,
int pageSize,
CancellationToken cancellationToken = default);
}

52
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageSender.cs

@ -0,0 +1,52 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Message;
public interface IPushPlusMessageSender
{
Task<string> SendWeChatAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default);
Task<string> SendEmailAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default);
Task<string> SendWeWorkAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default);
Task<string> SendWebhookAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default);
Task<string> SendSmsAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default);
}

88
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/MessageHttpClientExtensions.cs

@ -0,0 +1,88 @@
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Message;
internal static class MessageHttpClientExtensions
{
private const string _sendMessagTemplate = "{\"token\":\"$token\",\"title\":\"$title\",\"content\":\"$content\",\"topic\":\"$topic\",\"template\":\"$template\",\"channel\":\"$channel\",\"webhook\":\"$webhook\",\"callbackUrl\":\"$callbackUrl\",\"timestamp\":\"$timestamp\"}";
public async static Task<string> GetSendMessageContentAsync(
this HttpClient httpClient,
string token,
string title,
string content,
string topic = "",
string template = "html",
string channel = "wechat",
string webhook = "",
string callbackUrl = "",
string timestamp = "",
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/send");
var requestBody = _sendMessagTemplate
.Replace("$token", token)
.Replace("$title", title)
.Replace("$content", content)
.Replace("$topic", topic)
.Replace("$template", template)
.Replace("$channel", channel)
.Replace("$webhook", webhook)
.Replace("$callbackUrl", callbackUrl)
.Replace("$timestamp", timestamp);
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetSendResultContentAsync(
this HttpClient httpClient,
string accessKey,
string shortCode,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/message/sendMessageResult?shortCode={shortCode}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetMessageListContentAsync(
this HttpClient httpClient,
string accessKey,
int current,
int pageSize,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/message/list");
var requestBodyTemplate = "{\"current\":\"$current\",\"pageSize\":\"$pageSize\"}";
var requestBody = requestBodyTemplate
.Replace("$current", current.ToString())
.Replace("$pageSize", pageSize.ToString());
requestMessage.Content = new StringContent(requestBody);
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

44
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessage.cs

@ -0,0 +1,44 @@
using Newtonsoft.Json;
using System;
namespace LINGYUN.Abp.PushPlus.Message;
public class PushPlusMessage
{
/// <summary>
/// 消息发送渠道;
/// wechat-微信公众号,
/// mail-邮件,
/// cp-企业微信应用,
/// webhook-第三方webhook
/// </summary>
[JsonProperty("channel")]
public string Channel { get; set; }
/// <summary>
/// 消息类型;
/// 1-一对一消息,
/// 2-一对多消息
/// </summary>
[JsonProperty("messageType")]
public PushPlusMessageType MessageType { get; set; }
/// <summary>
/// 消息短链码;可用于查询消息发送结果
/// </summary>
[JsonProperty("shortCode")]
public string ShortCode { get; set; }
/// <summary>
/// 消息标题
/// </summary>
[JsonProperty("title")]
public string Title { get; set; }
/// <summary>
/// 群组名称,一对多消息才有值
/// </summary>
[JsonProperty("topicName")]
public string TopicName { get; set; }
/// <summary>
/// 更新日期
/// </summary>
[JsonProperty("updateTime")]
public DateTime UpdateTime { get; set; }
}

67
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageProvider.cs

@ -0,0 +1,67 @@
using LINGYUN.Abp.PushPlus.Token;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace LINGYUN.Abp.PushPlus.Message;
public class PushPlusMessageProvider : IPushPlusMessageProvider, ITransientDependency
{
protected ILogger<PushPlusMessageProvider> Logger { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IPushPlusTokenProvider PushPlusTokenProvider { get; }
public PushPlusMessageProvider(
ILogger<PushPlusMessageProvider> logger,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IPushPlusTokenProvider pushPlusTokenProvider)
{
Logger = logger;
JsonSerializer = jsonSerializer;
HttpClientFactory = httpClientFactory;
PushPlusTokenProvider = pushPlusTokenProvider;
}
public async virtual Task<PushPlusPagedResponse<PushPlusMessage>> GetMessageListAsync(
int current,
int pageSize,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetMessageListContentAsync(
token.AccessKey,
current,
pageSize,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusMessage>>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<SendPushPlusMessageResult> GetSendResultAsync(
string shortCode,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetSendResultContentAsync(
token.AccessKey,
shortCode,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<SendPushPlusMessageResult>>(content);
return pushPlusResponse.GetData();
}
}

214
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSender.cs

@ -0,0 +1,214 @@
using LINGYUN.Abp.Features.LimitValidation;
using LINGYUN.Abp.PushPlus.Channel;
using LINGYUN.Abp.PushPlus.Features;
using LINGYUN.Abp.PushPlus.Settings;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Json;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.PushPlus.Message;
[RequiresFeature(PushPlusFeatureNames.Message.Enable)]
public class PushPlusMessageSender : IPushPlusMessageSender, ITransientDependency
{
protected ILogger<PushPlusMessageSender> Logger { get; }
protected IClock Clock { get; }
protected IJsonSerializer JsonSerializer { get; }
protected ISettingProvider SettingProvider { get; }
protected IHttpClientFactory HttpClientFactory { get; }
public PushPlusMessageSender(
ILogger<PushPlusMessageSender> logger,
IClock clock,
IJsonSerializer jsonSerializer,
ISettingProvider settingProvider,
IHttpClientFactory httpClientFactory)
{
Logger = logger;
Clock = clock;
JsonSerializer = jsonSerializer;
SettingProvider = settingProvider;
HttpClientFactory = httpClientFactory;
}
[RequiresFeature(PushPlusFeatureNames.Channel.WeChat.Enable)]
[RequiresLimitFeature(
PushPlusFeatureNames.Channel.WeChat.SendLimit,
PushPlusFeatureNames.Channel.WeChat.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<string> SendWeChatAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
return await SendAsync(
title,
content,
topic,
template,
PushPlusChannelType.WeChat,
webhook,
callbackUrl,
cancellationToken);
}
[RequiresFeature(PushPlusFeatureNames.Channel.WeWork.Enable)]
[RequiresLimitFeature(
PushPlusFeatureNames.Channel.WeWork.SendLimit,
PushPlusFeatureNames.Channel.WeWork.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<string> SendWeWorkAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
return await SendAsync(
title,
content,
topic,
template,
PushPlusChannelType.WeWork,
webhook,
callbackUrl,
cancellationToken);
}
[RequiresFeature(PushPlusFeatureNames.Channel.Email.Enable)]
[RequiresLimitFeature(
PushPlusFeatureNames.Channel.Email.SendLimit,
PushPlusFeatureNames.Channel.Email.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<string> SendEmailAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
return await SendAsync(
title,
content,
topic,
template,
PushPlusChannelType.Email,
webhook,
callbackUrl,
cancellationToken);
}
[RequiresFeature(PushPlusFeatureNames.Channel.Webhook.Enable)]
[RequiresLimitFeature(
PushPlusFeatureNames.Channel.Webhook.SendLimit,
PushPlusFeatureNames.Channel.Webhook.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<string> SendWebhookAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
return await SendAsync(
title,
content,
topic,
template,
PushPlusChannelType.Webhook,
webhook,
callbackUrl,
cancellationToken);
}
[RequiresFeature(PushPlusFeatureNames.Channel.Sms.Enable)]
[RequiresLimitFeature(
PushPlusFeatureNames.Channel.Sms.SendLimit,
PushPlusFeatureNames.Channel.Sms.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<string> SendSmsAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
return await SendAsync(
title,
content,
topic,
template,
PushPlusChannelType.Sms,
webhook,
callbackUrl,
cancellationToken);
}
protected async virtual Task<string> SendAsync(
string title,
string content,
string topic = "",
PushPlusMessageTemplate template = PushPlusMessageTemplate.Html,
PushPlusChannelType channel = PushPlusChannelType.WeChat,
string webhook = "",
string callbackUrl = "",
CancellationToken cancellationToken = default)
{
Check.NotNullOrEmpty(title, nameof(title));
Check.NotNullOrEmpty(content, nameof(content));
var token = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.Token);
Check.NotNullOrEmpty(token, PushPlusSettingNames.Security.Token);
var client = HttpClientFactory.GetPushPlusClient();
var sendContent = await client.GetSendMessageContentAsync(
token,
title,
content,
topic,
GetTemplate(template),
channel.GetChannelName(),
webhook,
callbackUrl,
cancellationToken: cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<string>>(sendContent);
return pushPlusResponse.GetData();
}
private static string GetTemplate(PushPlusMessageTemplate template)
{
return template switch
{
PushPlusMessageTemplate.Html => "html",
PushPlusMessageTemplate.Text => "txt",
PushPlusMessageTemplate.Json => "json",
PushPlusMessageTemplate.Markdown => "markdown",
PushPlusMessageTemplate.CloudMonitor => "cloudMonitor",
PushPlusMessageTemplate.Jenkins => "jenkins",
PushPlusMessageTemplate.Route => "route",
_ => "html",
};
}
}

21
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageStatus.cs

@ -0,0 +1,21 @@
namespace LINGYUN.Abp.PushPlus.Message;
public enum PushPlusMessageStatus
{
/// <summary>
/// 未发送
/// </summary>
NotSend = 0,
/// <summary>
/// 发送中
/// </summary>
Sending = 1,
/// <summary>
/// 已发送
/// </summary>
Successed = 2,
/// <summary>
/// 发送失败
/// </summary>
Failed = 3,
}

35
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs

@ -0,0 +1,35 @@
namespace LINGYUN.Abp.PushPlus.Message;
/// <summary>
/// 模板(template)枚举。默认使用html模板
/// </summary>
public enum PushPlusMessageTemplate
{
/// <summary>
/// 默认模板,支持html文本
/// </summary>
Html = 0,
/// <summary>
/// 纯文本展示,不转义html
/// </summary>
Text = 1,
/// <summary>
/// 内容基于json格式展示
/// </summary>
Json = 2,
/// <summary>
/// 内容基于markdown格式展示
/// </summary>
Markdown = 3,
/// <summary>
/// 阿里云监控报警定制模板
/// </summary>
CloudMonitor = 4,
/// <summary>
/// jenkins插件定制模板
/// </summary>
Jenkins = 5,
/// <summary>
/// 路由器插件定制模板
/// </summary>
Route = 6
}

13
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageType.cs

@ -0,0 +1,13 @@
namespace LINGYUN.Abp.PushPlus.Message;
public enum PushPlusMessageType
{
/// <summary>
/// 一对一消息
/// </summary>
Once,
/// <summary>
/// 一对多消息
/// </summary>
Multiple
}

27
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/SendPushPlusMessageResult.cs

@ -0,0 +1,27 @@
using Newtonsoft.Json;
using System;
namespace LINGYUN.Abp.PushPlus.Message;
public class SendPushPlusMessageResult
{
/// <summary>
/// 消息投递状态
/// 0-未投递,
/// 1-发送中,
/// 2-已发送,
/// 3-发送失败
/// </summary>
[JsonProperty("status")]
public PushPlusMessageStatus Status { get; set; }
/// <summary>
/// 发送失败原因
/// </summary>
[JsonProperty("errorMessage")]
public string ErrorMessage { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[JsonProperty("updateTime")]
public DateTime UpdateTime { get; set; }
}

38
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusPagedResponse.cs

@ -0,0 +1,38 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace LINGYUN.Abp.PushPlus;
public class PushPlusPagedResponse<T>
{
/// <summary>
/// 当前页码
/// </summary>
[JsonProperty("pageNum")]
public int PageNum { get; set; }
/// <summary>
/// 分页大小
/// </summary>
[JsonProperty("pageSize")]
public int PageSize { get; set; }
/// <summary>
/// 总行数
/// </summary>
[JsonProperty("total")]
public int Total { get; set; }
/// <summary>
/// 总页数
/// </summary>
[JsonProperty("pages")]
public int Pages { get; set; }
/// <summary>
/// 消息列表
/// </summary>
[JsonProperty("list")]
public List<T> Items { get; set; }
public PushPlusPagedResponse()
{
Items = new List<T>();
}
}

14
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusRequestException.cs

@ -0,0 +1,14 @@
using Volo.Abp;
using Volo.Abp.ExceptionHandling;
namespace LINGYUN.Abp.PushPlus;
public class PushPlusRequestException : AbpException, IHasErrorCode
{
public string Code { get; }
public PushPlusRequestException(string code, string message)
: base($"The PushPlush API returns an error: {code} - {message}")
{
Code = code;
}
}

58
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusResponse.cs

@ -0,0 +1,58 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus;
public class PushPlusResponse<T>
{
/// <summary>
/// 状态码
/// </summary>
[JsonProperty("code")]
public string Code { get; set; }
/// <summary>
/// 错误消息
/// </summary>
[JsonProperty("msg")]
public string Message { get; set; }
/// <summary>
/// 返回数据
/// </summary>
[JsonProperty("data")]
public T Data { get; set; }
/// <summary>
/// 是否调用成功
/// </summary>
public bool IsSuccessed => string.Equals("200", Code);
public PushPlusResponse()
{
}
public PushPlusResponse(string code, string message)
{
Code = code;
Message = message;
}
public PushPlusResponse(string code, string message, T data)
{
Code = code;
Message = message;
Data = data;
}
public T GetData()
{
ThrowOfFailed();
return Data;
}
public void ThrowOfFailed()
{
if (!IsSuccessed)
{
throw new PushPlusRequestException(Code, Message);
}
}
}

38
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/IPushPlusChannelProvider.cs

@ -0,0 +1,38 @@
using LINGYUN.Abp.PushPlus.Channel;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Setting;
/// <summary>
/// 发送渠道接口
/// </summary>
public interface IPushPlusChannelProvider
{
/// <summary>
/// 获取默认发送渠道
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusChannel> GetDefaultChannelAsync(
CancellationToken cancellationToken = default);
/// <summary>
/// 修改默认发送渠道
/// </summary>
/// <param name="defaultChannel">默认渠道;wechat-微信公众号,mail-邮件,cp-企业微信应用,webhook-第三方webhook</param>
/// <param name="defaultWebhook">渠道参数;webhook和cp渠道需要填写具体的webhook编号或自定义编码</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task UpdateDefaultChannelAsync(
PushPlusChannelType defaultChannel,
string defaultWebhook = "",
CancellationToken cancellationToken = default);
/// <summary>
/// 修改接收消息限制
/// </summary>
/// <param name="recevieLimit">接收消息限制;0-接收全部,1-不接收消息</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task UpdateRecevieLimitAsync(
PushPlusChannelRecevieLimit recevieLimit,
CancellationToken cancellationToken = default);
}

35
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannel.cs

@ -0,0 +1,35 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Setting;
/// <summary>
/// 发送渠道
/// </summary>
public class PushPlusChannel
{
/// <summary>
/// 默认渠道编码
/// </summary>
[JsonProperty("defaultChannel")]
public string DefaultChannel { get; set; }
/// <summary>
/// 默认渠道名称
/// </summary>
[JsonProperty("defaultChannelTxt")]
public string DefaultChannelName { get; set; }
/// <summary>
/// 渠道参数
/// </summary>
[JsonProperty("defaultWebhook")]
public string DefaultWebhook { get; set; }
/// <summary>
/// 发送限制;0-无限制,1-禁止所有渠道发送,2-限制微信渠道,3-限制邮件渠道
/// </summary>
[JsonProperty("sendLimit")]
public PushPlusChannelRecevieLimit SendLimit { get; set; }
/// <summary>
/// 接收限制;0-接收全部,1-不接收消息
/// </summary>
[JsonProperty("recevieLimit")]
public PushPlusChannelRecevieLimit RecevieLimit { get; set; }
}

83
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelProvider.cs

@ -0,0 +1,83 @@
using LINGYUN.Abp.PushPlus.Channel;
using LINGYUN.Abp.PushPlus.Token;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace LINGYUN.Abp.PushPlus.Setting;
public class PushPlusChannelProvider : IPushPlusChannelProvider, ITransientDependency
{
protected ILogger<PushPlusChannelProvider> Logger { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IPushPlusTokenProvider PushPlusTokenProvider { get; }
public PushPlusChannelProvider(
ILogger<PushPlusChannelProvider> logger,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IPushPlusTokenProvider pushPlusTokenProvider)
{
Logger = logger;
JsonSerializer = jsonSerializer;
HttpClientFactory = httpClientFactory;
PushPlusTokenProvider = pushPlusTokenProvider;
}
public async virtual Task<PushPlusChannel> GetDefaultChannelAsync(CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetDefaultChannelContentAsync(
token.AccessKey,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusChannel>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task UpdateDefaultChannelAsync(
PushPlusChannelType defaultChannel,
string defaultWebhook = "",
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetUpdateDefaultChannelContentAsync(
token.AccessKey,
defaultChannel.GetChannelName(),
defaultWebhook,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<object>>(content);
pushPlusResponse.ThrowOfFailed();
}
public async virtual Task UpdateRecevieLimitAsync(
PushPlusChannelRecevieLimit recevieLimit,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetUpdateRecevieLimitContentAsync(
token.AccessKey,
recevieLimit,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<object>>(content);
pushPlusResponse.ThrowOfFailed();
}
}

15
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs

@ -0,0 +1,15 @@
namespace LINGYUN.Abp.PushPlus.Setting;
/// <summary>
/// 接收限制;0-接收全部,1-不接收消息
/// </summary>
public enum PushPlusChannelRecevieLimit
{
/// <summary>
/// 接收全部
/// </summary>
RecevieAll = 0,
/// <summary>
/// 不接收消息
/// </summary>
DontRecevied = 1
}

27
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs

@ -0,0 +1,27 @@
namespace LINGYUN.Abp.PushPlus.Setting;
/// <summary>
/// 发送限制;
/// 0-无限制,
/// 1-禁止所有渠道发送,
/// 2-限制微信渠道,
/// 3-限制邮件渠道
/// </summary>
public enum PushPlusChannelSendLimit
{
/// <summary>
/// 无限制
/// </summary>
None = 0,
/// <summary>
/// 禁止所有渠道发送
/// </summary>
ForbidAllChannel = 1,
/// <summary>
/// 限制微信渠道
/// </summary>
ForbidWeChatChannel = 2,
/// <summary>
/// 限制邮件渠道
/// </summary>
ForbidEmailChannel = 3,
}

65
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/SettingHttpClientExtensions.cs

@ -0,0 +1,65 @@
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Setting;
internal static class SettingHttpClientExtensions
{
public async static Task<string> GetDefaultChannelContentAsync(
this HttpClient httpClient,
string accessKey,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/open/setting/getUserSettings");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
private const string _updateDefaultChannelTemplate = "{\"defaultChannel\":\"$defaultChannel\",\"defaultWebhook\":\"$defaultWebhook\"}";
public async static Task<string> GetUpdateDefaultChannelContentAsync(
this HttpClient httpClient,
string accessKey,
string defaultChannel,
string defaultWebhook = "",
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/setting/changeDefaultChannel");
var requestBody = _updateDefaultChannelTemplate
.Replace("$defaultChannel", defaultChannel)
.Replace("$defaultWebhook", defaultWebhook);
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetUpdateRecevieLimitContentAsync(
this HttpClient httpClient,
string accessKey,
PushPlusChannelRecevieLimit recevieLimit,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/setting/changeRecevieLimit?recevieLimit=${(int)recevieLimit}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

42
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingDefinitionProvider.cs

@ -0,0 +1,42 @@
using LINGYUN.Abp.PushPlus.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.PushPlus.Settings;
public class PushPlusSettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(new[]
{
new SettingDefinition(
name: PushPlusSettingNames.Security.Token,
defaultValue: "qGE8NZ8rrQYj207kSv9vb5XzG1a+iK6z8yGPICjx3cY5p8bGcmMav5t0DRLjCprA",
displayName: L("Settings:Security.Token"),
description: L("Settings:Security.TokenDesc"),
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: PushPlusSettingNames.Security.SecretKey,
defaultValue: "HXGIfCpkUNonrOB8znJzNcDoKvZBKpNZ0tN38tktgrg=",
displayName: L("Settings:Security.SecretKey"),
description: L("Settings:Security.SecretKeyDesc"),
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
});
}
public static ILocalizableString L(string name)
{
return LocalizableString.Create<PushPlusResource>(name);
}
}

15
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingNames.cs

@ -0,0 +1,15 @@
namespace LINGYUN.Abp.PushPlus.Settings;
public static class PushPlusSettingNames
{
public const string Prefix = "PushPlus";
public static class Security
{
public const string Prefix = PushPlusSettingNames.Prefix + ".Security";
public const string Token = Prefix + ".Token";
public const string SecretKey = Prefix + ".SecretKey";
}
}

16
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/IPushPlusTokenProvider.cs

@ -0,0 +1,16 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Token;
/// <summary>
/// 获取AccessKey
/// </summary>
public interface IPushPlusTokenProvider
{
/// <summary>
/// 获取全局可用Token
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusToken> GetTokenAsync(CancellationToken cancellationToken = default);
}

28
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusToken.cs

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Token;
public class PushPlusToken
{
/// <summary>
/// 访问令牌,后续请求需加到header中
/// </summary>
[JsonProperty("accessKey")]
public string AccessKey { get; set; }
/// <summary>
/// 过期时间,过期后需要重新获取
/// </summary>
[JsonProperty("expiresIn")]
public int ExpiresIn { get; set; }
public PushPlusToken()
{
}
public PushPlusToken(string accessKey, int expiresIn)
{
AccessKey = accessKey;
ExpiresIn = expiresIn;
}
}

26
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenCacheItem.cs

@ -0,0 +1,26 @@
namespace LINGYUN.Abp.PushPlus.Token;
public class PushPlusTokenCacheItem
{
public const string KeyFormat = "t:{0};s:{1}";
public string AccessKey { get; set; }
public int ExpiresIn { get; set; }
public PushPlusTokenCacheItem()
{
}
public PushPlusTokenCacheItem(string accessKey, int expiresIn)
{
AccessKey = accessKey;
ExpiresIn = expiresIn;
}
public static string CalculateCacheKey(string token, string secretKey)
{
return string.Format(KeyFormat, token, secretKey);
}
}

104
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenProvider.cs

@ -0,0 +1,104 @@
using LINGYUN.Abp.PushPlus.Settings;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.PushPlus.Token;
public class PushPlusTokenProvider : IPushPlusTokenProvider, ITransientDependency
{
public ILogger<PushPlusTokenProvider> Logger { protected get; set; }
protected IDistributedCache<PushPlusTokenCacheItem> Cache { get; }
protected IJsonSerializer JsonSerializer { get; }
protected ISettingProvider SettingProvider { get; }
protected IHttpClientFactory HttpClientFactory { get; }
public PushPlusTokenProvider(
IDistributedCache<PushPlusTokenCacheItem> cache,
IJsonSerializer jsonSerializer,
ISettingProvider settingProvider,
IHttpClientFactory httpClientFactory)
{
Cache = cache;
JsonSerializer = jsonSerializer;
SettingProvider = settingProvider;
HttpClientFactory = httpClientFactory;
Logger = NullLogger<PushPlusTokenProvider>.Instance;
}
public async virtual Task<PushPlusToken> GetTokenAsync(CancellationToken cancellationToken = default)
{
var token = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.Token);
var secretKey = await SettingProvider.GetOrNullAsync(PushPlusSettingNames.Security.SecretKey);
Check.NotNullOrEmpty(token, PushPlusSettingNames.Security.Token);
Check.NotNullOrEmpty(secretKey, PushPlusSettingNames.Security.SecretKey);
return await GetTokenAsync(token, secretKey, cancellationToken);
}
public async virtual Task<PushPlusToken> GetTokenAsync(
string token,
string secretKey,
CancellationToken cancellationToken = default)
{
var cacheItem = await GetCacheItemAsync(token, secretKey, cancellationToken);
return new PushPlusToken(cacheItem.AccessKey, cacheItem.ExpiresIn);
}
protected async virtual Task<PushPlusTokenCacheItem> GetCacheItemAsync(
string token,
string secretKey,
CancellationToken cancellationToken = default)
{
var cacheKey = PushPlusTokenCacheItem.CalculateCacheKey(token, secretKey);
Logger.LogDebug($"PushPlusTokenProvider.GetCacheItemAsync: {cacheKey}");
var cacheItem = await Cache.GetAsync(cacheKey, token: cancellationToken);
if (cacheItem != null)
{
Logger.LogDebug($"Found in the cache: {cacheKey}");
return cacheItem;
}
Logger.LogDebug($"Not found in the cache, getting from the httpClient: {cacheKey}");
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTokenContentAsync(token, secretKey, cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<PushPlusToken>>(content);
var pushPlusToken = pushPlusResponse.GetData();
cacheItem = new PushPlusTokenCacheItem(
pushPlusToken.AccessKey,
pushPlusToken.ExpiresIn);
await Cache.SetAsync(
cacheKey,
cacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(pushPlusToken.ExpiresIn - 120)
},
token: cancellationToken);
Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
return cacheItem;
}
}

29
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/TokenHttpClientExtensions.cs

@ -0,0 +1,29 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Token;
internal static class TokenHttpClientExtensions
{
public async static Task<string> GetTokenContentAsync(
this HttpClient httpClient,
string token,
string secretKey,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/common/openApi/getAccessKey");
var requestBodyTemplate = "{\"token\":\"$token\",\"secretKey\":\"$secretKey\"}";
var requestBody = requestBodyTemplate
.Replace("$token", token)
.Replace("$secretKey", secretKey);
requestMessage.Content = new StringContent(requestBody);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

100
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/IPushPlusTopicProvider.cs

@ -0,0 +1,100 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Topic;
/// <summary>
/// 群组接口
/// </summary>
public interface IPushPlusTopicProvider
{
/// <summary>
/// 获取群组列表
/// </summary>
/// <param name="current">当前所在分页数</param>
/// <param name="pageSize">每页大小,最大值为50</param>
/// <param name="topicType">群组类型;0-我创建的,1-我加入的</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusPagedResponse<PushPlusTopic>> GetTopicListAsync(
int current,
int pageSize,
PushPlusTopicType topicType = PushPlusTopicType.Create,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取我创建的群组详情
/// </summary>
/// <param name="topicId">群组编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusTopicProfile> GetTopicProfileAsync(
int topicId,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取我加入的群详情
/// </summary>
/// <param name="topicId">群组编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusTopicForMe> GetTopicForMeProfileAsync(
int topicId,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增群组
/// </summary>
/// <param name="topicCode">群组编码</param>
/// <param name="topicName">群组名称</param>
/// <param name="contact">联系方式</param>
/// <param name="introduction">群组简介</param>
/// <param name="receiptMessage">加入后回复内容</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<int> CreateTopicAsync(
string topicCode,
string topicName,
string contact,
string introduction,
string receiptMessage = "",
CancellationToken cancellationToken = default);
/// <summary>
/// 获取群组二维码
/// </summary>
/// <param name="topicId">群组编号</param>
/// <param name="forever">二维码类型;0-临时二维码,1-永久二维码</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusTopicQrCode> GetTopicQrCodeAsync(
int topicId,
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary,
CancellationToken cancellationToken = default);
/// <summary>
/// 退出群组
/// </summary>
/// <param name="topicId">群组编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> QuitTopicAsync(
int topicId,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取群组内用户
/// </summary>
/// <param name="current">当前所在分页数</param>
/// <param name="pageSize">每页大小,最大值为50</param>
/// <param name="topicId">群组编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusPagedResponse<PushPlusTopicUser>> GetSubscriberListAsync(
int current,
int pageSize,
int topicId,
CancellationToken cancellationToken = default);
/// <summary>
/// 删除群组内用户
/// </summary>
/// <param name="topicRelationId">用户编号</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> UnSubscriberAsync(
int topicRelationId,
CancellationToken cancellationToken = default);
}

30
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopic.cs

@ -0,0 +1,30 @@
using Newtonsoft.Json;
using System;
namespace LINGYUN.Abp.PushPlus.Topic;
/// <summary>
/// 群组
/// </summary>
public class PushPlusTopic
{
/// <summary>
/// 群组编号
/// </summary>
[JsonProperty("topicId")]
public int TopicId { get; set; }
/// <summary>
/// 群组编码
/// </summary>
[JsonProperty("topicCode")]
public string TopicCode { get; set; }
/// <summary>
/// 群组名称
/// </summary>
[JsonProperty("topicName")]
public string TopicName { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[JsonProperty("createTime")]
public DateTime CreateTime { get; set; }
}

19
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicForMe.cs

@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Topic;
/// <summary>
/// 我加入的群详情
/// </summary>
public class PushPlusTopicForMe : PushPlusTopic
{
/// <summary>
/// 联系方式
/// </summary>
[JsonProperty("contact")]
public string Contact { get; set; }
/// <summary>
/// 群组简介
/// </summary>
[JsonProperty("introduction")]
public string Introduction { get; set; }
}

34
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProfile.cs

@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Topic;
/// <summary>
/// 群组详情
/// </summary>
public class PushPlusTopicProfile : PushPlusTopic
{
/// <summary>
/// 永久二维码图片地址
/// </summary>
[JsonProperty("qrCodeImgUrl")]
public string QrCodeImgUrl { get; set; }
/// <summary>
/// 联系方式
/// </summary>
[JsonProperty("contact")]
public string Contact { get; set; }
/// <summary>
/// 群组简介
/// </summary>
[JsonProperty("introduction")]
public string Introduction { get; set; }
/// <summary>
/// 加入后回复内容
/// </summary>
[JsonProperty("receiptMessage")]
public string ReceiptMessage { get; set; }
/// <summary>
/// 群组订阅人总数
/// </summary>
[JsonProperty("topicUserCount")]
public string TopicUserCount { get; set; }
}

196
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProvider.cs

@ -0,0 +1,196 @@
using LINGYUN.Abp.PushPlus.Token;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace LINGYUN.Abp.PushPlus.Topic;
public class PushPlusTopicProvider : IPushPlusTopicProvider, ITransientDependency
{
protected ILogger<PushPlusTopicProvider> Logger { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IPushPlusTokenProvider PushPlusTokenProvider { get; }
public PushPlusTopicProvider(
ILogger<PushPlusTopicProvider> logger,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IPushPlusTokenProvider pushPlusTokenProvider)
{
Logger = logger;
JsonSerializer = jsonSerializer;
HttpClientFactory = httpClientFactory;
PushPlusTokenProvider = pushPlusTokenProvider;
}
public async virtual Task<int> CreateTopicAsync(
string topicCode,
string topicName,
string contact,
string introduction,
string receiptMessage = "",
CancellationToken cancellationToken = default)
{
Check.NotNullOrWhiteSpace(topicCode, nameof(topicCode));
Check.NotNullOrWhiteSpace(topicName, nameof(topicName));
Check.NotNullOrWhiteSpace(contact, nameof(contact));
Check.NotNullOrWhiteSpace(introduction, nameof(introduction));
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetCreateTopicContentAsync(
token.AccessKey,
topicCode,
topicName,
contact,
introduction,
receiptMessage,
cancellationToken);
var pushPlusResponse = JsonSerializer.Deserialize<PushPlusResponse<int>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusTopicForMe> GetTopicForMeProfileAsync(
int topicId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTopicForMeProfileContentAsync(
token.AccessKey,
topicId,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusTopicForMe>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusPagedResponse<PushPlusTopic>> GetTopicListAsync(
int current,
int pageSize,
PushPlusTopicType topicType = PushPlusTopicType.Create,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTopicListContentAsync(
token.AccessKey,
current,
pageSize,
topicType,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusTopic>>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusTopicProfile> GetTopicProfileAsync(
int topicId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTopicProfileContentAsync(
token.AccessKey,
topicId,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusTopicProfile>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusTopicQrCode> GetTopicQrCodeAsync(
int topicId,
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTopicQrCodeContentAsync(
token.AccessKey,
topicId,
forever,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusTopicQrCode>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<string> QuitTopicAsync(
int topicId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetQuitTopicContentAsync(
token.AccessKey,
topicId,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<string>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusPagedResponse<PushPlusTopicUser>> GetSubscriberListAsync(
int current,
int pageSize,
int topicId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetSubscriberListContentAsync(
token.AccessKey,
current,
pageSize,
topicId,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusPagedResponse<PushPlusTopicUser>>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<string> UnSubscriberAsync(
int topicRelationId,
CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetUnSubscriberContentAsync(
token.AccessKey,
topicRelationId,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<string>>(content);
return pushPlusResponse.GetData();
}
}

19
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCode.cs

@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Topic;
public class PushPlusTopicQrCode
{
/// <summary>
/// 群组二维码图片路径
/// </summary>
[JsonProperty("qrCodeImgUrl")]
public string QrCodeImgUrl { get; set; }
/// <summary>
/// 二维码类型;
/// 0-临时二维码,
/// 1-永久二维码
/// </summary>
[JsonProperty("forever")]
public PushPlusTopicQrCodeType Forever { get; set; }
}

12
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCodeType.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.PushPlus.Topic;
public enum PushPlusTopicQrCodeType
{
/// <summary>
/// 临时
/// </summary>
Temporary = 0,
/// <summary>
/// 永久
/// </summary>
Permanent = 1,
}

12
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicType.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.PushPlus.Topic;
public enum PushPlusTopicType
{
/// <summary>
/// 创建的
/// </summary>
Create = 0,
/// <summary>
/// 加入的
/// </summary>
Join = 1
}

48
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicUser.cs

@ -0,0 +1,48 @@
using LINGYUN.Abp.PushPlus.User;
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.Topic;
public class PushPlusTopicUser
{
/// <summary>
/// 用户编号;可用于删除用户
/// </summary>
[JsonProperty("id")]
public int Id { get; set; }
/// <summary>
/// 昵称
/// </summary>
[JsonProperty("nickName")]
public string NickName { get; set; }
/// <summary>
/// 用户微信openId
/// </summary>
[JsonProperty("openId")]
public string OpenId { get; set; }
/// <summary>
/// 头像url地址
/// </summary>
[JsonProperty("headImgUrl")]
public string HeadImgUrl { get; set; }
/// <summary>
/// 头像url地址
/// </summary>
[JsonProperty("userSex")]
public PushPlusUserSex? UserSex { get; set; }
/// <summary>
/// 是否绑定手机;0-未绑定,1-已绑定
/// </summary>
[JsonProperty("havePhone")]
public PushPlusUserPhoneBindStatus? HavePhone { get; set; }
/// <summary>
/// 是否关注微信公众号;0-未关注,1-已关注
/// </summary>
[JsonProperty("isFollow")]
public PushPlusUserFollowStatus? IsFollow { get; set; }
/// <summary>
/// 邮箱验证状态;0-未验证,1-待验证,2-已验证
/// </summary>
[JsonProperty("emailStatus")]
public PushPlusUserEmailStatus? EmailStatus { get; set; }
}

177
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/TopicHttpClientExtensions.cs

@ -0,0 +1,177 @@
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.Topic;
internal static class TopicHttpClientExtensions
{
private const string _getTopicListTemplate = "{\"current\":$current,\"pageSize\":$pageSize,\"params\":{\"topicType\":$topicType}}";
public async static Task<string> GetTopicListContentAsync(
this HttpClient httpClient,
string accessKey,
int current,
int pageSize,
PushPlusTopicType topicType = PushPlusTopicType.Create,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/topic/list");
var requestBody = _getTopicListTemplate
.Replace("$current", current.ToString())
.Replace("$pageSize", pageSize.ToString())
.Replace("$topicType", ((int)topicType).ToString());
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetTopicProfileContentAsync(
this HttpClient httpClient,
string accessKey,
int topicId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/topic/detail?topicId={topicId}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetTopicForMeProfileContentAsync(
this HttpClient httpClient,
string accessKey,
int topicId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/topic/joinTopicDetail?topicId={topicId}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
private const string _createTopicTemplate = "{\"topicCode\":\"$topicCode\",\"topicName\":\"$topicName\",\"contact\":\"$contact\",\"introduction\":\"$introduction\",\"receiptMessage\":\"$receiptMessage\"}";
public async static Task<string> GetCreateTopicContentAsync(
this HttpClient httpClient,
string accessKey,
string topicCode,
string topicName,
string contact,
string introduction,
string receiptMessage = "",
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/topic/add");
var requestBody = _createTopicTemplate
.Replace("$topicCode", topicCode)
.Replace("$topicName", topicName)
.Replace("$contact", contact)
.Replace("$introduction", introduction)
.Replace("$receiptMessage", receiptMessage);
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetTopicQrCodeContentAsync(
this HttpClient httpClient,
string accessKey,
int topicId,
PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/topic/qrCode?topicId={topicId}&forever={(int)forever}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetQuitTopicContentAsync(
this HttpClient httpClient,
string accessKey,
int topicId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/topic/exitTopic?topicId={topicId}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
private const string _getSubscriberListTemplate = "{\"current\":$current,\"pageSize\":$pageSize,\"params\":{\"topicId\":$topicId}}";
public async static Task<string> GetSubscriberListContentAsync(
this HttpClient httpClient,
string accessKey,
int current,
int pageSize,
int topicId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/open/topicUser/subscriberList");
var requestBody = _getSubscriberListTemplate
.Replace("$current", current.ToString())
.Replace("$pageSize", pageSize.ToString())
.Replace("$topicId", topicId.ToString());
requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetUnSubscriberContentAsync(
this HttpClient httpClient,
string accessKey,
int topicRelationId,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/open/topicUser/deleteTopicUser?topicRelationId={topicRelationId}");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

28
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/IPushPlusUserProvider.cs

@ -0,0 +1,28 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.User;
/// <summary>
/// 用户接口
/// </summary>
public interface IPushPlusUserProvider
{
/// <summary>
/// 获取token
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> GetMyTokenAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 个人资料详情
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusUserProfile> GetMyProfileAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取解封剩余时间
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PushPlusUserLimitTime> GetMyLimitTimeAsync(CancellationToken cancellationToken = default);
}

17
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserEmailStatus.cs

@ -0,0 +1,17 @@
namespace LINGYUN.Abp.PushPlus.User;
public enum PushPlusUserEmailStatus
{
/// <summary>
/// 未验证
/// </summary>
None = 0,
/// <summary>
/// 待验证
/// </summary>
UnConfirmed = 1,
/// <summary>
/// 已验证
/// </summary>
Confirmed = 2,
}

12
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserFollowStatus.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.PushPlus.User;
public enum PushPlusUserFollowStatus
{
/// <summary>
/// 已关注
/// </summary>
Follow = 0,
/// <summary>
/// 未关注
/// </summary>
NoFollow = 1,
}

20
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserLimitTime.cs

@ -0,0 +1,20 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.PushPlus.User;
public class PushPlusUserLimitTime
{
/// <summary>
/// 发送限制状态;
/// 1-无限制,
/// 2-短期限制,
/// 3-永久限制
/// </summary>
[JsonProperty("sendLimit")]
public PushPlusUserSendLimit SendLimit { get; set; }
/// <summary>
/// 解封时间
/// </summary>
[JsonProperty("userLimitTime")]
public string UserLimitTime { get; set; }
}

12
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserPhoneBindStatus.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.PushPlus.User;
public enum PushPlusUserPhoneBindStatus
{
/// <summary>
/// 绑定
/// </summary>
Bind = 0,
/// <summary>
/// 未绑定
/// </summary>
NoBind = 1,
}

69
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProfile.cs

@ -0,0 +1,69 @@
using Newtonsoft.Json;
using System;
namespace LINGYUN.Abp.PushPlus.User;
public class PushPlusUserProfile
{
/// <summary>
/// 用户微信的openId
/// </summary>
[JsonProperty("openId")]
public string OpenId { get; set; }
/// <summary>
/// 用户微信的unionId
/// </summary>
[JsonProperty("unionId")]
public string UnionId { get; set; }
/// <summary>
/// 昵称
/// </summary>
[JsonProperty("nickName")]
public string NickName { get; set; }
/// <summary>
/// 头像
/// </summary>
[JsonProperty("headImgUrl")]
public string HeadImgUrl { get; set; }
/// <summary>
/// 性别;
/// 0-未设置,
/// 1-男,
/// 2-女
/// </summary>
[JsonProperty("userSex")]
public PushPlusUserSex? Sex { get; set; }
/// <summary>
/// 用户令牌
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
/// <summary>
/// 手机号
/// </summary>
[JsonProperty("phoneNumber")]
public string PhoneNumber { get; set; }
/// <summary>
/// 邮箱
/// </summary>
[JsonProperty("email")]
public string Email { get; set; }
/// <summary>
/// 邮箱验证状态;
/// 0-未验证,
/// 1-待验证,
/// 2-已验证
/// </summary>
[JsonProperty("emailStatus")]
public PushPlusUserEmailStatus? EmailStatus { get; set; }
/// <summary>
/// 生日
/// </summary>
[JsonProperty("birthday")]
public DateTime? Birthday { get; set; }
/// <summary>
/// 用户积分
/// </summary>
[JsonProperty("points")]
public int? Points { get; set; }
}

74
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProvider.cs

@ -0,0 +1,74 @@
using LINGYUN.Abp.PushPlus.Token;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Json;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.PushPlus.User;
public class PushPlusUserProvider : IPushPlusUserProvider, ITransientDependency
{
protected ILogger<PushPlusUserProvider> Logger { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IPushPlusTokenProvider PushPlusTokenProvider { get; }
public PushPlusUserProvider(
ILogger<PushPlusUserProvider> logger,
IJsonSerializer jsonSerializer,
IHttpClientFactory httpClientFactory,
IPushPlusTokenProvider pushPlusTokenProvider)
{
Logger = logger;
JsonSerializer = jsonSerializer;
HttpClientFactory = httpClientFactory;
PushPlusTokenProvider = pushPlusTokenProvider;
}
public async virtual Task<PushPlusUserLimitTime> GetMyLimitTimeAsync(CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetLimitTimeContentAsync(
token.AccessKey,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusUserLimitTime>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<PushPlusUserProfile> GetMyProfileAsync(CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetProfileContentAsync(
token.AccessKey,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<PushPlusUserProfile>>(content);
return pushPlusResponse.GetData();
}
public async virtual Task<string> GetMyTokenAsync(CancellationToken cancellationToken = default)
{
var token = await PushPlusTokenProvider.GetTokenAsync();
var client = HttpClientFactory.GetPushPlusClient();
var content = await client.GetTokenContentAsync(
token.AccessKey,
cancellationToken);
var pushPlusResponse = JsonSerializer
.Deserialize<PushPlusResponse<string>>(content);
return pushPlusResponse.GetData();
}
}

17
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSendLimit.cs

@ -0,0 +1,17 @@
namespace LINGYUN.Abp.PushPlus.User;
public enum PushPlusUserSendLimit
{
/// <summary>
/// 未限制
/// </summary>
None = 1,
/// <summary>
/// 短期限制
/// </summary>
Short = 2,
/// <summary>
/// 永久限制
/// </summary>
Permanent = 3,
}

16
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSex.cs

@ -0,0 +1,16 @@
namespace LINGYUN.Abp.PushPlus.User;
public enum PushPlusUserSex
{
/// <summary>
/// 未设置
/// </summary>
NoSet = 0,
/// <summary>
/// 男
/// </summary>
Man =1,
/// <summary>
/// 女
/// </summary>
Woman = 2,
}

56
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/UserHttpClientExtensions.cs

@ -0,0 +1,56 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.PushPlus.User;
internal static class UserHttpClientExtensions
{
public async static Task<string> GetTokenContentAsync(
this HttpClient httpClient,
string accessKey,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/open/user/token");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetProfileContentAsync(
this HttpClient httpClient,
string accessKey,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"api/open/user/myInfo");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
public async static Task<string> GetLimitTimeContentAsync(
this HttpClient httpClient,
string accessKey,
CancellationToken cancellationToken = default)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"api/open/user/userLimitTime");
requestMessage.Headers.TryAddWithoutValidation("access-key", accessKey);
var httpResponse = await httpClient.SendAsync(requestMessage, cancellationToken);
return await httpResponse.Content.ReadAsStringAsync();
}
}

19
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/Microsoft/Extensions/DependencyInjection/IServiceConnectionExtensions.cs

@ -0,0 +1,19 @@
using System;
namespace Microsoft.Extensions.DependencyInjection;
internal static class IServiceConnectionExtensions
{
public static IServiceCollection AddPushPlusClient(
this IServiceCollection services)
{
services.AddHttpClient(
"_Abp_PushPlus_Client",
(httpClient) =>
{
httpClient.BaseAddress = new Uri("https://www.pushplus.plus");
});
return services;
}
}

42
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/README.md

@ -0,0 +1,42 @@
# LINGYUN.Abp.PushPlus
集成PushPlus
实现PushPlus相关Api文档,拥有PushPlus开放能力
详情见PushPlus文档: https://www.pushplus.plus/doc/guide/openApi.html#%E6%96%87%E6%A1%A3%E8%AF%B4%E6%98%8E
## 模块引用
```csharp
[DependsOn(typeof(AbpPushPlusModule))]
public class YouProjectModule : AbpModule
{
// other
}
```
## Features
* PushPlus PushPlus特性分组
* PushPlus.Message.Enable 全局启用PushPlus消息通道
* PushPlus.Channel.WeChat 微信公众号消息通道
* PushPlus.Channel.WeChat.Enable 启用微信公众号消息通道
* PushPlus.Channel.WeChat.SendLimit 微信公众号消息通道限制次数
* PushPlus.Channel.WeChat.SendLimitInterval 微信公众号消息通道限制周期(天)
* PushPlus.Channel.WeWork 企业微信消息通道
* PushPlus.Channel.WeWork.Enable 启用企业微信消息通道
* PushPlus.Channel.WeWork.SendLimit 企业微信消息通道限制次数
* PushPlus.Channel.WeWork.SendLimitInterval 企业微信消息通道限制周期(天)
* PushPlus.Channel.Webhook Webhook消息通道
* PushPlus.Channel.Webhook.Enable 启用Webhook消息通道
* PushPlus.Channel.Webhook.SendLimit Webhook消息通道限制次数
* PushPlus.Channel.Webhook.SendLimitInterval Webhook消息通道限制周期(天)
* PushPlus.Channel.Email Email消息通道
* PushPlus.Channel.Email.Enable 启用Email消息通道
* PushPlus.Channel.Email.SendLimit Email消息通道限制次数
* PushPlus.Channel.Email.SendLimitInterval Email消息通道限制周期(天)
* PushPlus.Channel.Sms 短信消息通道
* PushPlus.Channel.Sms.Enable 启用短信消息通道
* PushPlus.Channel.Sms.SendLimit 短信消息通道限制次数
* PushPlus.Channel.Sms.SendLimitInterval 短信消息通道限制周期(天)

10
aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/System/Net/Http/IHttpClientFactoryExtensions.cs

@ -0,0 +1,10 @@
namespace System.Net.Http;
internal static class IHttpClientFactoryExtensions
{
public static HttpClient GetPushPlusClient(
this IHttpClientFactory httpClientFactory)
{
return httpClientFactory.CreateClient("_Abp_PushPlus_Client");
}
}

18
aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj

@ -0,0 +1,18 @@
<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\pushplus\LINGYUN.Abp.PushPlus\LINGYUN.Abp.PushPlus.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" />
</ItemGroup>
</Project>

7
aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestBase.cs

@ -0,0 +1,7 @@
using LINGYUN.Abp.Tests;
namespace LINGYUN.Abp.PushPlus;
public class AbpPushPlusTestBase : AbpTestsBase<AbpPushPlusTestModule>
{
}

24
aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestModule.cs

@ -0,0 +1,24 @@
using LINGYUN.Abp.Tests;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.PushPlus;
[DependsOn(
typeof(AbpPushPlusModule),
typeof(AbpTestsBaseModule))]
public class AbpPushPlusTestModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configurationOptions = new AbpConfigurationBuilderOptions
{
BasePath = @"D:\Projects\Development\Abp\PushPlus",
EnvironmentName = "Test"
};
var configuration = ConfigurationHelper.BuildConfiguration(configurationOptions);
context.Services.ReplaceConfiguration(configuration);
}
}

40
aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSenderTests.cs

@ -0,0 +1,40 @@
using System.Threading.Tasks;
using Xunit;
using Shouldly;
namespace LINGYUN.Abp.PushPlus.Message;
/// <summary>
/// TODO: 接入webhook测试
/// TODO: 接入企业微信测试
/// </summary>
public class PushPlusMessageSenderTests : AbpPushPlusTestBase
{
protected IPushPlusMessageSender PushPlusMessageSender { get; }
public PushPlusMessageSenderTests()
{
PushPlusMessageSender = GetRequiredService<IPushPlusMessageSender>();
}
[Theory]
[InlineData("Title from the Xunit unit test", "Content from the Xunit unit test.")]
public async virtual Task Send_To_We_Chat_Test(
string title,
string content)
{
var result = await PushPlusMessageSender.SendWeChatAsync(title, content);
result.ShouldNotBeNullOrWhiteSpace();
}
[Theory]
[InlineData("Title from the Xunit unit test", "Content from the Xunit unit test.")]
public async virtual Task Send_To_Email_Test(
string title,
string content)
{
var result = await PushPlusMessageSender.SendEmailAsync(title, content);
result.ShouldNotBeNullOrWhiteSpace();
}
}

7
aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs

@ -21,9 +21,12 @@ namespace LINGYUN.Abp.Tests.Features
{
var feature = FeatureDefinitionManager.Get(name);
var featureFunc = FakeFeatureOptions.FeatureMaps[name];
if (FakeFeatureOptions.FeatureMaps.TryGetValue(name, out var featureFunc))
{
return Task.FromResult(featureFunc(feature));
}
return Task.FromResult(feature.DefaultValue);
}
}
}

Loading…
Cancel
Save