From c2f5da5fbd9cc7dfbf7997eb3a3fa26e2ffe0989 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Thu, 25 Aug 2022 20:03:53 +0800 Subject: [PATCH] feature: Integrated PushPlus. --- aspnet-core/LINGYUN.MicroService.Common.sln | 38 ++++ .../FodyWeavers.xml | 3 + .../LINGYUN.Abp.Notifications.PushPlus.csproj | 20 ++ .../NotificationDefinitionExtensions.cs | 36 +++ .../AbpNotificationsPushPlusModule.cs | 18 ++ .../PushPlusNotificationPublishProvider.cs | 88 +++++++ .../README.md | 16 ++ .../LINGYUN.Abp.PushPlus/FodyWeavers.xml | 3 + .../LINGYUN.Abp.PushPlus/FodyWeavers.xsd | 30 +++ .../LINGYUN.Abp.PushPlus.csproj | 29 +++ .../LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs | 53 +++++ .../PushPlus/Channel/PushPlusChannelType.cs | 27 +++ .../Channel/PushPlusChannelTypeExtensions.cs | 16 ++ .../Webhook/IPushPlusWebhookProvider.cs | 62 +++++ .../Channel/Webhook/PushPlusWebhook.cs | 44 ++++ .../Webhook/PushPlusWebhookProvider.cs | 125 ++++++++++ .../Channel/Webhook/PushPlusWebhookType.cs | 20 ++ .../Webhook/WebhookHttpClientExtensions.cs | 108 +++++++++ .../PushPlusFeatureDefinitionProvider.cs | 168 ++++++++++++++ .../PushPlus/Features/PushPlusFeatureNames.cs | 103 +++++++++ .../PushPlus/Localization/PushPlusResource.cs | 8 + .../PushPlus/Localization/Resources/en.json | 47 ++++ .../Localization/Resources/zh-Hans.json | 47 ++++ .../Message/IPushPlusMessageProvider.cs | 28 +++ .../Message/IPushPlusMessageSender.cs | 52 +++++ .../Message/MessageHttpClientExtensions.cs | 88 +++++++ .../Abp/PushPlus/Message/PushPlusMessage.cs | 44 ++++ .../Message/PushPlusMessageProvider.cs | 67 ++++++ .../PushPlus/Message/PushPlusMessageSender.cs | 214 ++++++++++++++++++ .../PushPlus/Message/PushPlusMessageStatus.cs | 21 ++ .../Message/PushPlusMessageTemplate.cs | 35 +++ .../PushPlus/Message/PushPlusMessageType.cs | 13 ++ .../Message/SendPushPlusMessageResult.cs | 27 +++ .../Abp/PushPlus/PushPlusPagedResponse.cs | 38 ++++ .../Abp/PushPlus/PushPlusRequestException.cs | 14 ++ .../LINGYUN/Abp/PushPlus/PushPlusResponse.cs | 58 +++++ .../Setting/IPushPlusChannelProvider.cs | 38 ++++ .../Abp/PushPlus/Setting/PushPlusChannel.cs | 35 +++ .../Setting/PushPlusChannelProvider.cs | 83 +++++++ .../Setting/PushPlusChannelRecevieLimit.cs | 15 ++ .../Setting/PushPlusChannelSendLimit.cs | 27 +++ .../Setting/SettingHttpClientExtensions.cs | 65 ++++++ .../PushPlusSettingDefinitionProvider.cs | 42 ++++ .../PushPlus/Settings/PushPlusSettingNames.cs | 15 ++ .../PushPlus/Token/IPushPlusTokenProvider.cs | 16 ++ .../Abp/PushPlus/Token/PushPlusToken.cs | 28 +++ .../PushPlus/Token/PushPlusTokenCacheItem.cs | 26 +++ .../PushPlus/Token/PushPlusTokenProvider.cs | 104 +++++++++ .../Token/TokenHttpClientExtensions.cs | 29 +++ .../PushPlus/Topic/IPushPlusTopicProvider.cs | 100 ++++++++ .../Abp/PushPlus/Topic/PushPlusTopic.cs | 30 +++ .../Abp/PushPlus/Topic/PushPlusTopicForMe.cs | 19 ++ .../PushPlus/Topic/PushPlusTopicProfile.cs | 34 +++ .../PushPlus/Topic/PushPlusTopicProvider.cs | 196 ++++++++++++++++ .../Abp/PushPlus/Topic/PushPlusTopicQrCode.cs | 19 ++ .../PushPlus/Topic/PushPlusTopicQrCodeType.cs | 12 + .../Abp/PushPlus/Topic/PushPlusTopicType.cs | 12 + .../Abp/PushPlus/Topic/PushPlusTopicUser.cs | 48 ++++ .../Topic/TopicHttpClientExtensions.cs | 177 +++++++++++++++ .../PushPlus/User/IPushPlusUserProvider.cs | 28 +++ .../PushPlus/User/PushPlusUserEmailStatus.cs | 17 ++ .../PushPlus/User/PushPlusUserFollowStatus.cs | 12 + .../PushPlus/User/PushPlusUserLimitTime.cs | 20 ++ .../User/PushPlusUserPhoneBindStatus.cs | 12 + .../Abp/PushPlus/User/PushPlusUserProfile.cs | 69 ++++++ .../Abp/PushPlus/User/PushPlusUserProvider.cs | 74 ++++++ .../PushPlus/User/PushPlusUserSendLimit.cs | 17 ++ .../Abp/PushPlus/User/PushPlusUserSex.cs | 16 ++ .../PushPlus/User/UserHttpClientExtensions.cs | 56 +++++ .../IServiceConnectionExtensions.cs | 19 ++ .../pushplus/LINGYUN.Abp.PushPlus/README.md | 42 ++++ .../Net/Http/IHttpClientFactoryExtensions.cs | 10 + .../LINGYUN.Abp.PushPlus.Tests.csproj | 18 ++ .../Abp/PushPlus/AbpPushPlusTestBase.cs | 7 + .../Abp/PushPlus/AbpPushPlusTestModule.cs | 24 ++ .../Message/PushPlusMessageSenderTests.cs | 40 ++++ .../Abp/Tests/Features/FakeFeatureStore.cs | 7 +- 77 files changed, 3464 insertions(+), 2 deletions(-) create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/NotificationDefinitionExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/AbpNotificationsPushPlusModule.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/README.md create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xml create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xsd create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelTypeExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/IPushPlusWebhookProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhook.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookType.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/WebhookHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureDefinitionProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureNames.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/PushPlusResource.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/en.json create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageSender.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/MessageHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessage.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSender.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageStatus.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageType.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/SendPushPlusMessageResult.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusPagedResponse.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusRequestException.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusResponse.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/IPushPlusChannelProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannel.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/SettingHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingDefinitionProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingNames.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/IPushPlusTokenProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusToken.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenCacheItem.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/TokenHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/IPushPlusTopicProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopic.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicForMe.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProfile.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCode.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCodeType.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicType.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicUser.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/TopicHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/IPushPlusUserProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserEmailStatus.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserFollowStatus.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserLimitTime.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserPhoneBindStatus.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProfile.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProvider.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSendLimit.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSex.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/UserHttpClientExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/Microsoft/Extensions/DependencyInjection/IServiceConnectionExtensions.cs create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/README.md create mode 100644 aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/System/Net/Http/IHttpClientFactoryExtensions.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSenderTests.cs diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index 8fc9805b8..6ea7b8d2d 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/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} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj new file mode 100644 index 000000000..043ae1b8c --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN.Abp.Notifications.PushPlus.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/NotificationDefinitionExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/NotificationDefinitionExtensions.cs new file mode 100644 index 000000000..8d93ece6c --- /dev/null +++ b/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 +{ + /// + /// 消息群发群组编码 + /// 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 + /// + /// 群组编码 + /// + /// + /// + /// + public static NotificationDefinition WithTopic( + this NotificationDefinition notification, + string topic) + { + return notification.WithProperty("topic", topic); + } + /// + /// 获取消息群发群组编码 + /// + /// + /// + /// 通知定义的群组编码,未定义返回null + /// + public static string GetTopicOrNull( + this NotificationDefinition notification) + { + if (notification.Properties.TryGetValue("topic", out var topicDefine) == true) + { + return topicDefine.ToString(); + } + + return null; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/AbpNotificationsPushPlusModule.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/AbpNotificationsPushPlusModule.cs new file mode 100644 index 000000000..58eb09ec4 --- /dev/null +++ b/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(options => + { + options.PublishProviders.Add(); + }); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs new file mode 100644 index 000000000..ec69e5363 --- /dev/null +++ b/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 localizationOptions, + INotificationDefinitionManager notificationDefinitionManager) + { + PushPlusMessageSender = pushPlusMessageSender; + LocalizerFactory = localizerFactory; + LocalizationOptions = localizationOptions.Value; + NotificationDefinitionManager = notificationDefinitionManager; + } + + protected async override Task PublishAsync( + NotificationInfo notification, + IEnumerable 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(); + var titleResource = GetResource(titleInfo.ResourceName); + var title = LocalizerFactory.Create(titleResource.ResourceType)[titleInfo.Name, titleInfo.Values].Value; + + var messageInfo = notification.Data.TryGetData("message").As(); + 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)); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/README.md b/aspnet-core/modules/pushplus/LINGYUN.Abp.Notifications.PushPlus/README.md new file mode 100644 index 000000000..a1b59cf25 --- /dev/null +++ b/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 +} +``` + diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xml b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xsd b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj new file mode 100644 index 000000000..93331c526 --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN.Abp.PushPlus.csproj @@ -0,0 +1,29 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/AbpPushPlusModule.cs new file mode 100644 index 000000000..e04247263 --- /dev/null +++ b/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(options => + { + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>>(); + + options.UnsupportedTypes.TryAdd>(); + + options.UnsupportedTypes.TryAdd>(); + + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>>(); + options.UnsupportedTypes.TryAdd>>(); + + options.UnsupportedTypes.TryAdd>(); + options.UnsupportedTypes.TryAdd>(); + }); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs new file mode 100644 index 000000000..a5886b2a9 --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelType.cs @@ -0,0 +1,27 @@ +namespace LINGYUN.Abp.PushPlus.Channel; +/// +/// 发送渠道(channel)枚举 +/// +public enum PushPlusChannelType +{ + /// + /// 微信公众号 + /// + WeChat = 0, + /// + /// 第三方webhook + /// + Webhook = 1, + /// + /// 企业微信应用 + /// + WeWork = 2, + /// + /// 邮箱 + /// + Email = 3, + /// + /// 短信 + /// + Sms = 4 +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelTypeExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/PushPlusChannelTypeExtensions.cs new file mode 100644 index 000000000..437afb756 --- /dev/null +++ b/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", + }; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/IPushPlusWebhookProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/IPushPlusWebhookProvider.cs new file mode 100644 index 000000000..04cb4f6b6 --- /dev/null +++ b/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; +/// +/// Webhook配置接口 +/// +public interface IPushPlusWebhookProvider +{ + /// + /// 获取webhook列表 + /// + /// 当前所在分页数 + /// 每页大小,最大值为50 + /// + /// + Task> GetWebhookListAsync( + int current = 1, + int pageSize = 20, + CancellationToken cancellationToken = default); + /// + /// webhook详情 + /// + /// webhook编号 + /// + /// + Task GetWebhookAsync( + int webhookId, + CancellationToken cancellationToken = default); + /// + /// 新增webhook + /// + /// webhook编码 + /// webhook名称 + /// webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱 + /// 调用的url地址 + /// + /// + Task CreateWebhookAsync( + string webhookCode, + string webhookName, + PushPlusWebhookType webhookType, + string webhookUrl, + CancellationToken cancellationToken = default); + /// + /// 修改webhook配置 + /// + /// webhook编号 + /// webhook编码 + /// webhook名称 + /// webhook类型;1-企业微信,2-钉钉,3-飞书,4-server酱 + /// 调用的url地址 + /// + /// + Task UpdateWebhookAsync( + int id, + string webhookCode, + string webhookName, + PushPlusWebhookType webhookType, + string webhookUrl, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhook.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhook.cs new file mode 100644 index 000000000..8af28f890 --- /dev/null +++ b/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; +/// +/// webhook +/// +public class PushPlusWebhook +{ + /// + /// webhook编号 + /// + [JsonProperty("id")] + public int Id { get; set; } + /// + /// webhook编码 + /// + [JsonProperty("webhookCode")] + public string WebhookCode { get; set; } + /// + /// webhook名称 + /// + [JsonProperty("webhookName")] + public string WebhookName { get; set; } + /// + /// webhook类型; + /// 1-企业微信, + /// 2-钉钉, + /// 3-飞书, + /// 4-server酱 + /// + [JsonProperty("webhookType")] + public PushPlusWebhookType WebhookType { get; set; } + /// + /// 调用的url地址 + /// + [JsonProperty("webhookUrl")] + public string WebhookUrl { get; set; } + /// + /// 创建日期 + /// + [JsonProperty("createTime")] + public DateTime CreateTime { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookProvider.cs new file mode 100644 index 000000000..8bccafb56 --- /dev/null +++ b/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 Logger { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IPushPlusTokenProvider PushPlusTokenProvider { get; } + + public PushPlusWebhookProvider( + ILogger logger, + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IPushPlusTokenProvider pushPlusTokenProvider) + { + Logger = logger; + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + PushPlusTokenProvider = pushPlusTokenProvider; + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task> 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>>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookType.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/PushPlusWebhookType.cs new file mode 100644 index 000000000..5619622bc --- /dev/null +++ b/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 +{ + /// + /// 企业微信 + /// + WeWork = 1, + /// + /// 钉钉 + /// + DingTalk = 2, + /// + /// 飞书 + /// + FeiShu = 3, + /// + /// Server酱 + /// + SctFtqq = 4, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/WebhookHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Channel/Webhook/WebhookHttpClientExtensions.cs new file mode 100644 index 000000000..35e6cdab5 --- /dev/null +++ b/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 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 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 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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureDefinitionProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureDefinitionProvider.cs new file mode 100644 index 000000000..57797b2eb --- /dev/null +++ b/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(name); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureNames.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Features/PushPlusFeatureNames.cs new file mode 100644 index 000000000..73036c45d --- /dev/null +++ b/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"; + /// + /// 启用微信通道 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string SendLimit = GroupName + ".SendLimit"; + /// + /// 发送次数上限时长 + /// + public const string SendLimitInterval = GroupName + ".SendLimitInterval"; + } + + public static class WeWork + { + public const string GroupName = Channel.GroupName + ".WeWork"; + /// + /// 启用企业微信通道 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string SendLimit = GroupName + ".SendLimit"; + /// + /// 发送次数上限时长 + /// + public const string SendLimitInterval = GroupName + ".SendLimitInterval"; + } + + public static class Webhook + { + public const string GroupName = Channel.GroupName + ".Webhook"; + /// + /// 启用Webhook通道 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string SendLimit = GroupName + ".SendLimit"; + /// + /// 发送次数上限时长 + /// + public const string SendLimitInterval = GroupName + ".SendLimitInterval"; + } + + public static class Email + { + public const string GroupName = Channel.GroupName + ".Email"; + /// + /// 启用Email通道 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string SendLimit = GroupName + ".SendLimit"; + /// + /// 发送次数上限时长 + /// + public const string SendLimitInterval = GroupName + ".SendLimitInterval"; + } + + public static class Sms + { + public const string GroupName = Channel.GroupName + ".Sms"; + /// + /// 启用Sms通道 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string SendLimit = GroupName + ".SendLimit"; + /// + /// 发送次数上限时长 + /// + public const string SendLimitInterval = GroupName + ".SendLimitInterval"; + } + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/PushPlusResource.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/PushPlusResource.cs new file mode 100644 index 000000000..2a3b4f21e --- /dev/null +++ b/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 +{ +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/en.json b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/en.json new file mode 100644 index 000000000..c89f605bf --- /dev/null +++ b/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)." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/zh-Hans.json b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..90782486e --- /dev/null +++ b/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": "设置短信渠道限制周期(时间刻度: 天)." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageProvider.cs new file mode 100644 index 000000000..8705d0f6d --- /dev/null +++ b/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 +{ + /// + /// 查询消息发送结果 + /// + /// 消息短链码 + /// + /// + Task GetSendResultAsync( + string shortCode, + CancellationToken cancellationToken = default); + /// + /// 消息列表 + /// + /// 当前所在分页数 + /// 每页大小,最大值为50 + /// + /// + Task> GetMessageListAsync( + int current, + int pageSize, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageSender.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/IPushPlusMessageSender.cs new file mode 100644 index 000000000..0d7d11844 --- /dev/null +++ b/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 SendWeChatAsync( + string title, + string content, + string topic = "", + PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, + string webhook = "", + string callbackUrl = "", + CancellationToken cancellationToken = default); + + Task SendEmailAsync( + string title, + string content, + string topic = "", + PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, + string webhook = "", + string callbackUrl = "", + CancellationToken cancellationToken = default); + + Task SendWeWorkAsync( + string title, + string content, + string topic = "", + PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, + string webhook = "", + string callbackUrl = "", + CancellationToken cancellationToken = default); + + Task SendWebhookAsync( + string title, + string content, + string topic = "", + PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, + string webhook = "", + string callbackUrl = "", + CancellationToken cancellationToken = default); + + Task SendSmsAsync( + string title, + string content, + string topic = "", + PushPlusMessageTemplate template = PushPlusMessageTemplate.Html, + string webhook = "", + string callbackUrl = "", + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/MessageHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/MessageHttpClientExtensions.cs new file mode 100644 index 000000000..0b4837165 --- /dev/null +++ b/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 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 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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessage.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessage.cs new file mode 100644 index 000000000..d0ca1c805 --- /dev/null +++ b/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 +{ + /// + /// 消息发送渠道; + /// wechat-微信公众号, + /// mail-邮件, + /// cp-企业微信应用, + /// webhook-第三方webhook + /// + [JsonProperty("channel")] + public string Channel { get; set; } + /// + /// 消息类型; + /// 1-一对一消息, + /// 2-一对多消息 + /// + [JsonProperty("messageType")] + public PushPlusMessageType MessageType { get; set; } + /// + /// 消息短链码;可用于查询消息发送结果 + /// + [JsonProperty("shortCode")] + public string ShortCode { get; set; } + /// + /// 消息标题 + /// + [JsonProperty("title")] + public string Title { get; set; } + /// + /// 群组名称,一对多消息才有值 + /// + [JsonProperty("topicName")] + public string TopicName { get; set; } + /// + /// 更新日期 + /// + [JsonProperty("updateTime")] + public DateTime UpdateTime { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageProvider.cs new file mode 100644 index 000000000..c5ae6735e --- /dev/null +++ b/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 Logger { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IPushPlusTokenProvider PushPlusTokenProvider { get; } + + public PushPlusMessageProvider( + ILogger logger, + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IPushPlusTokenProvider pushPlusTokenProvider) + { + Logger = logger; + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + PushPlusTokenProvider = pushPlusTokenProvider; + } + + public async virtual Task> 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>>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSender.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSender.cs new file mode 100644 index 000000000..81dadca10 --- /dev/null +++ b/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 Logger { get; } + protected IClock Clock { get; } + protected IJsonSerializer JsonSerializer { get; } + protected ISettingProvider SettingProvider { get; } + protected IHttpClientFactory HttpClientFactory { get; } + + public PushPlusMessageSender( + ILogger 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 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 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 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 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 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 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>(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", + }; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageStatus.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageStatus.cs new file mode 100644 index 000000000..953fb9077 --- /dev/null +++ b/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 +{ + /// + /// 未发送 + /// + NotSend = 0, + /// + /// 发送中 + /// + Sending = 1, + /// + /// 已发送 + /// + Successed = 2, + /// + /// 发送失败 + /// + Failed = 3, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs new file mode 100644 index 000000000..d9936694a --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageTemplate.cs @@ -0,0 +1,35 @@ +namespace LINGYUN.Abp.PushPlus.Message; +/// +/// 模板(template)枚举。默认使用html模板 +/// +public enum PushPlusMessageTemplate +{ + /// + /// 默认模板,支持html文本 + /// + Html = 0, + /// + /// 纯文本展示,不转义html + /// + Text = 1, + /// + /// 内容基于json格式展示 + /// + Json = 2, + /// + /// 内容基于markdown格式展示 + /// + Markdown = 3, + /// + /// 阿里云监控报警定制模板 + /// + CloudMonitor = 4, + /// + /// jenkins插件定制模板 + /// + Jenkins = 5, + /// + /// 路由器插件定制模板 + /// + Route = 6 +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageType.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/PushPlusMessageType.cs new file mode 100644 index 000000000..fd4143c4b --- /dev/null +++ b/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 +{ + /// + /// 一对一消息 + /// + Once, + /// + /// 一对多消息 + /// + Multiple +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/SendPushPlusMessageResult.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Message/SendPushPlusMessageResult.cs new file mode 100644 index 000000000..702f9af07 --- /dev/null +++ b/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 +{ + /// + /// 消息投递状态 + /// 0-未投递, + /// 1-发送中, + /// 2-已发送, + /// 3-发送失败 + /// + [JsonProperty("status")] + public PushPlusMessageStatus Status { get; set; } + /// + /// 发送失败原因 + /// + [JsonProperty("errorMessage")] + public string ErrorMessage { get; set; } + /// + /// 更新时间 + /// + [JsonProperty("updateTime")] + public DateTime UpdateTime { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusPagedResponse.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusPagedResponse.cs new file mode 100644 index 000000000..23828db56 --- /dev/null +++ b/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 +{ + /// + /// 当前页码 + /// + [JsonProperty("pageNum")] + public int PageNum { get; set; } + /// + /// 分页大小 + /// + [JsonProperty("pageSize")] + public int PageSize { get; set; } + /// + /// 总行数 + /// + [JsonProperty("total")] + public int Total { get; set; } + /// + /// 总页数 + /// + [JsonProperty("pages")] + public int Pages { get; set; } + /// + /// 消息列表 + /// + [JsonProperty("list")] + public List Items { get; set; } + + public PushPlusPagedResponse() + { + Items = new List(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusRequestException.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusRequestException.cs new file mode 100644 index 000000000..2696758c9 --- /dev/null +++ b/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; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusResponse.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/PushPlusResponse.cs new file mode 100644 index 000000000..c1057e46e --- /dev/null +++ b/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 +{ + /// + /// 状态码 + /// + [JsonProperty("code")] + public string Code { get; set; } + /// + /// 错误消息 + /// + [JsonProperty("msg")] + public string Message { get; set; } + /// + /// 返回数据 + /// + [JsonProperty("data")] + public T Data { get; set; } + /// + /// 是否调用成功 + /// + 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); + } + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/IPushPlusChannelProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/IPushPlusChannelProvider.cs new file mode 100644 index 000000000..c2404cc9d --- /dev/null +++ b/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; +/// +/// 发送渠道接口 +/// +public interface IPushPlusChannelProvider +{ + /// + /// 获取默认发送渠道 + /// + /// + /// + Task GetDefaultChannelAsync( + CancellationToken cancellationToken = default); + /// + /// 修改默认发送渠道 + /// + /// 默认渠道;wechat-微信公众号,mail-邮件,cp-企业微信应用,webhook-第三方webhook + /// 渠道参数;webhook和cp渠道需要填写具体的webhook编号或自定义编码 + /// + /// + Task UpdateDefaultChannelAsync( + PushPlusChannelType defaultChannel, + string defaultWebhook = "", + CancellationToken cancellationToken = default); + /// + /// 修改接收消息限制 + /// + /// 接收消息限制;0-接收全部,1-不接收消息 + /// + /// + Task UpdateRecevieLimitAsync( + PushPlusChannelRecevieLimit recevieLimit, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannel.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannel.cs new file mode 100644 index 000000000..c3e9b64c2 --- /dev/null +++ b/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; +/// +/// 发送渠道 +/// +public class PushPlusChannel +{ + /// + /// 默认渠道编码 + /// + [JsonProperty("defaultChannel")] + public string DefaultChannel { get; set; } + /// + /// 默认渠道名称 + /// + [JsonProperty("defaultChannelTxt")] + public string DefaultChannelName { get; set; } + /// + /// 渠道参数 + /// + [JsonProperty("defaultWebhook")] + public string DefaultWebhook { get; set; } + /// + /// 发送限制;0-无限制,1-禁止所有渠道发送,2-限制微信渠道,3-限制邮件渠道 + /// + [JsonProperty("sendLimit")] + public PushPlusChannelRecevieLimit SendLimit { get; set; } + /// + /// 接收限制;0-接收全部,1-不接收消息 + /// + [JsonProperty("recevieLimit")] + public PushPlusChannelRecevieLimit RecevieLimit { get; set; } + +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelProvider.cs new file mode 100644 index 000000000..0f92d9d5d --- /dev/null +++ b/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 Logger { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IPushPlusTokenProvider PushPlusTokenProvider { get; } + + public PushPlusChannelProvider( + ILogger logger, + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IPushPlusTokenProvider pushPlusTokenProvider) + { + Logger = logger; + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + PushPlusTokenProvider = pushPlusTokenProvider; + } + + public async virtual Task 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>(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>(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>(content); + + pushPlusResponse.ThrowOfFailed(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs new file mode 100644 index 000000000..4c888e401 --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelRecevieLimit.cs @@ -0,0 +1,15 @@ +namespace LINGYUN.Abp.PushPlus.Setting; +/// +/// 接收限制;0-接收全部,1-不接收消息 +/// +public enum PushPlusChannelRecevieLimit +{ + /// + /// 接收全部 + /// + RecevieAll = 0, + /// + /// 不接收消息 + /// + DontRecevied = 1 +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs new file mode 100644 index 000000000..4e50c1073 --- /dev/null +++ b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/PushPlusChannelSendLimit.cs @@ -0,0 +1,27 @@ +namespace LINGYUN.Abp.PushPlus.Setting; +/// +/// 发送限制; +/// 0-无限制, +/// 1-禁止所有渠道发送, +/// 2-限制微信渠道, +/// 3-限制邮件渠道 +/// +public enum PushPlusChannelSendLimit +{ + /// + /// 无限制 + /// + None = 0, + /// + /// 禁止所有渠道发送 + /// + ForbidAllChannel = 1, + /// + /// 限制微信渠道 + /// + ForbidWeChatChannel = 2, + /// + /// 限制邮件渠道 + /// + ForbidEmailChannel = 3, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/SettingHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Setting/SettingHttpClientExtensions.cs new file mode 100644 index 000000000..b6d218ccb --- /dev/null +++ b/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 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 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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingDefinitionProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingDefinitionProvider.cs new file mode 100644 index 000000000..c1fc6dc58 --- /dev/null +++ b/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(name); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingNames.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Settings/PushPlusSettingNames.cs new file mode 100644 index 000000000..99ea91ea7 --- /dev/null +++ b/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"; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/IPushPlusTokenProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/IPushPlusTokenProvider.cs new file mode 100644 index 000000000..8976e4bff --- /dev/null +++ b/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; +/// +/// 获取AccessKey +/// +public interface IPushPlusTokenProvider +{ + /// + /// 获取全局可用Token + /// + /// + /// + Task GetTokenAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusToken.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusToken.cs new file mode 100644 index 000000000..5a27a05bb --- /dev/null +++ b/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 +{ + /// + /// 访问令牌,后续请求需加到header中 + /// + [JsonProperty("accessKey")] + public string AccessKey { get; set; } + /// + /// 过期时间,过期后需要重新获取 + /// + [JsonProperty("expiresIn")] + public int ExpiresIn { get; set; } + + public PushPlusToken() + { + + } + + public PushPlusToken(string accessKey, int expiresIn) + { + AccessKey = accessKey; + ExpiresIn = expiresIn; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenCacheItem.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenCacheItem.cs new file mode 100644 index 000000000..5f6a460f1 --- /dev/null +++ b/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); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/PushPlusTokenProvider.cs new file mode 100644 index 000000000..3a5937e08 --- /dev/null +++ b/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 Logger { protected get; set; } + + protected IDistributedCache Cache { get; } + protected IJsonSerializer JsonSerializer { get; } + protected ISettingProvider SettingProvider { get; } + protected IHttpClientFactory HttpClientFactory { get; } + + public PushPlusTokenProvider( + IDistributedCache cache, + IJsonSerializer jsonSerializer, + ISettingProvider settingProvider, + IHttpClientFactory httpClientFactory) + { + Cache = cache; + JsonSerializer = jsonSerializer; + SettingProvider = settingProvider; + HttpClientFactory = httpClientFactory; + + Logger = NullLogger.Instance; + } + + public async virtual Task 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 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 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>(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; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/TokenHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Token/TokenHttpClientExtensions.cs new file mode 100644 index 000000000..46a4bb0fc --- /dev/null +++ b/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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/IPushPlusTopicProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/IPushPlusTopicProvider.cs new file mode 100644 index 000000000..5597125ee --- /dev/null +++ b/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; +/// +/// 群组接口 +/// +public interface IPushPlusTopicProvider +{ + /// + /// 获取群组列表 + /// + /// 当前所在分页数 + /// 每页大小,最大值为50 + /// 群组类型;0-我创建的,1-我加入的 + /// + /// + Task> GetTopicListAsync( + int current, + int pageSize, + PushPlusTopicType topicType = PushPlusTopicType.Create, + CancellationToken cancellationToken = default); + /// + /// 获取我创建的群组详情 + /// + /// 群组编号 + /// + /// + Task GetTopicProfileAsync( + int topicId, + CancellationToken cancellationToken = default); + /// + /// 获取我加入的群详情 + /// + /// 群组编号 + /// + /// + Task GetTopicForMeProfileAsync( + int topicId, + CancellationToken cancellationToken = default); + /// + /// 新增群组 + /// + /// 群组编码 + /// 群组名称 + /// 联系方式 + /// 群组简介 + /// 加入后回复内容 + /// + /// + Task CreateTopicAsync( + string topicCode, + string topicName, + string contact, + string introduction, + string receiptMessage = "", + CancellationToken cancellationToken = default); + /// + /// 获取群组二维码 + /// + /// 群组编号 + /// 二维码类型;0-临时二维码,1-永久二维码 + /// + /// + Task GetTopicQrCodeAsync( + int topicId, + PushPlusTopicQrCodeType forever = PushPlusTopicQrCodeType.Temporary, + CancellationToken cancellationToken = default); + /// + /// 退出群组 + /// + /// 群组编号 + /// + /// + Task QuitTopicAsync( + int topicId, + CancellationToken cancellationToken = default); + /// + /// 获取群组内用户 + /// + /// 当前所在分页数 + /// 每页大小,最大值为50 + /// 群组编号 + /// + /// + Task> GetSubscriberListAsync( + int current, + int pageSize, + int topicId, + CancellationToken cancellationToken = default); + /// + /// 删除群组内用户 + /// + /// 用户编号 + /// + /// + Task UnSubscriberAsync( + int topicRelationId, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopic.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopic.cs new file mode 100644 index 000000000..8cdbd2787 --- /dev/null +++ b/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; +/// +/// 群组 +/// +public class PushPlusTopic +{ + /// + /// 群组编号 + /// + [JsonProperty("topicId")] + public int TopicId { get; set; } + /// + /// 群组编码 + /// + [JsonProperty("topicCode")] + public string TopicCode { get; set; } + /// + /// 群组名称 + /// + [JsonProperty("topicName")] + public string TopicName { get; set; } + /// + /// 创建时间 + /// + [JsonProperty("createTime")] + public DateTime CreateTime { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicForMe.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicForMe.cs new file mode 100644 index 000000000..f7dc882c2 --- /dev/null +++ b/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; +/// +/// 我加入的群详情 +/// +public class PushPlusTopicForMe : PushPlusTopic +{ + /// + /// 联系方式 + /// + [JsonProperty("contact")] + public string Contact { get; set; } + /// + /// 群组简介 + /// + [JsonProperty("introduction")] + public string Introduction { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProfile.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProfile.cs new file mode 100644 index 000000000..a9f04697c --- /dev/null +++ b/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; +/// +/// 群组详情 +/// +public class PushPlusTopicProfile : PushPlusTopic +{ + /// + /// 永久二维码图片地址 + /// + [JsonProperty("qrCodeImgUrl")] + public string QrCodeImgUrl { get; set; } + /// + /// 联系方式 + /// + [JsonProperty("contact")] + public string Contact { get; set; } + /// + /// 群组简介 + /// + [JsonProperty("introduction")] + public string Introduction { get; set; } + /// + /// 加入后回复内容 + /// + [JsonProperty("receiptMessage")] + public string ReceiptMessage { get; set; } + /// + /// 群组订阅人总数 + /// + [JsonProperty("topicUserCount")] + public string TopicUserCount { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicProvider.cs new file mode 100644 index 000000000..693338854 --- /dev/null +++ b/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 Logger { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IPushPlusTokenProvider PushPlusTokenProvider { get; } + + public PushPlusTopicProvider( + ILogger logger, + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IPushPlusTokenProvider pushPlusTokenProvider) + { + Logger = logger; + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + PushPlusTokenProvider = pushPlusTokenProvider; + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task> 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>>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task> 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>>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCode.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCode.cs new file mode 100644 index 000000000..17cbe6cb4 --- /dev/null +++ b/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 +{ + /// + /// 群组二维码图片路径 + /// + [JsonProperty("qrCodeImgUrl")] + public string QrCodeImgUrl { get; set; } + /// + /// 二维码类型; + /// 0-临时二维码, + /// 1-永久二维码 + /// + [JsonProperty("forever")] + public PushPlusTopicQrCodeType Forever { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCodeType.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicQrCodeType.cs new file mode 100644 index 000000000..87db1f58f --- /dev/null +++ b/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 +{ + /// + /// 临时 + /// + Temporary = 0, + /// + /// 永久 + /// + Permanent = 1, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicType.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicType.cs new file mode 100644 index 000000000..72101d5a4 --- /dev/null +++ b/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 +{ + /// + /// 创建的 + /// + Create = 0, + /// + /// 加入的 + /// + Join = 1 +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicUser.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/PushPlusTopicUser.cs new file mode 100644 index 000000000..3eec790b8 --- /dev/null +++ b/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 +{ + /// + /// 用户编号;可用于删除用户 + /// + [JsonProperty("id")] + public int Id { get; set; } + /// + /// 昵称 + /// + [JsonProperty("nickName")] + public string NickName { get; set; } + /// + /// 用户微信openId + /// + [JsonProperty("openId")] + public string OpenId { get; set; } + /// + /// 头像url地址 + /// + [JsonProperty("headImgUrl")] + public string HeadImgUrl { get; set; } + /// + /// 头像url地址 + /// + [JsonProperty("userSex")] + public PushPlusUserSex? UserSex { get; set; } + /// + /// 是否绑定手机;0-未绑定,1-已绑定 + /// + [JsonProperty("havePhone")] + public PushPlusUserPhoneBindStatus? HavePhone { get; set; } + /// + /// 是否关注微信公众号;0-未关注,1-已关注 + /// + [JsonProperty("isFollow")] + public PushPlusUserFollowStatus? IsFollow { get; set; } + /// + /// 邮箱验证状态;0-未验证,1-待验证,2-已验证 + /// + [JsonProperty("emailStatus")] + public PushPlusUserEmailStatus? EmailStatus { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/TopicHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/Topic/TopicHttpClientExtensions.cs new file mode 100644 index 000000000..23265456a --- /dev/null +++ b/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 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 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 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 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 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 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 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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/IPushPlusUserProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/IPushPlusUserProvider.cs new file mode 100644 index 000000000..71c816dd8 --- /dev/null +++ b/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; +/// +/// 用户接口 +/// +public interface IPushPlusUserProvider +{ + /// + /// 获取token + /// + /// + /// + Task GetMyTokenAsync(CancellationToken cancellationToken = default); + /// + /// 个人资料详情 + /// + /// + /// + Task GetMyProfileAsync(CancellationToken cancellationToken = default); + /// + /// 获取解封剩余时间 + /// + /// + /// + Task GetMyLimitTimeAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserEmailStatus.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserEmailStatus.cs new file mode 100644 index 000000000..9d09031fa --- /dev/null +++ b/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 +{ + /// + /// 未验证 + /// + None = 0, + /// + /// 待验证 + /// + UnConfirmed = 1, + /// + /// 已验证 + /// + Confirmed = 2, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserFollowStatus.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserFollowStatus.cs new file mode 100644 index 000000000..d754675cd --- /dev/null +++ b/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 +{ + /// + /// 已关注 + /// + Follow = 0, + /// + /// 未关注 + /// + NoFollow = 1, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserLimitTime.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserLimitTime.cs new file mode 100644 index 000000000..8b10699e4 --- /dev/null +++ b/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 +{ + /// + /// 发送限制状态; + /// 1-无限制, + /// 2-短期限制, + /// 3-永久限制 + /// + [JsonProperty("sendLimit")] + public PushPlusUserSendLimit SendLimit { get; set; } + /// + /// 解封时间 + /// + [JsonProperty("userLimitTime")] + public string UserLimitTime { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserPhoneBindStatus.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserPhoneBindStatus.cs new file mode 100644 index 000000000..6ca31b95b --- /dev/null +++ b/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 +{ + /// + /// 绑定 + /// + Bind = 0, + /// + /// 未绑定 + /// + NoBind = 1, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProfile.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProfile.cs new file mode 100644 index 000000000..88a4d7140 --- /dev/null +++ b/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 +{ + /// + /// 用户微信的openId + /// + [JsonProperty("openId")] + public string OpenId { get; set; } + /// + /// 用户微信的unionId + /// + [JsonProperty("unionId")] + public string UnionId { get; set; } + /// + /// 昵称 + /// + [JsonProperty("nickName")] + public string NickName { get; set; } + /// + /// 头像 + /// + [JsonProperty("headImgUrl")] + public string HeadImgUrl { get; set; } + /// + /// 性别; + /// 0-未设置, + /// 1-男, + /// 2-女 + /// + [JsonProperty("userSex")] + public PushPlusUserSex? Sex { get; set; } + /// + /// 用户令牌 + /// + [JsonProperty("token")] + public string Token { get; set; } + /// + /// 手机号 + /// + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + /// + /// 邮箱 + /// + [JsonProperty("email")] + public string Email { get; set; } + /// + /// 邮箱验证状态; + /// 0-未验证, + /// 1-待验证, + /// 2-已验证 + /// + [JsonProperty("emailStatus")] + public PushPlusUserEmailStatus? EmailStatus { get; set; } + /// + /// 生日 + /// + [JsonProperty("birthday")] + public DateTime? Birthday { get; set; } + /// + /// 用户积分 + /// + [JsonProperty("points")] + public int? Points { get; set; } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProvider.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserProvider.cs new file mode 100644 index 000000000..cb089a032 --- /dev/null +++ b/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 Logger { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IPushPlusTokenProvider PushPlusTokenProvider { get; } + + public PushPlusUserProvider( + ILogger logger, + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IPushPlusTokenProvider pushPlusTokenProvider) + { + Logger = logger; + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + PushPlusTokenProvider = pushPlusTokenProvider; + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } + + public async virtual Task 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>(content); + + return pushPlusResponse.GetData(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSendLimit.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSendLimit.cs new file mode 100644 index 000000000..58eb635f8 --- /dev/null +++ b/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 +{ + /// + /// 未限制 + /// + None = 1, + /// + /// 短期限制 + /// + Short = 2, + /// + /// 永久限制 + /// + Permanent = 3, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSex.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/PushPlusUserSex.cs new file mode 100644 index 000000000..e7aafec21 --- /dev/null +++ b/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 +{ + /// + /// 未设置 + /// + NoSet = 0, + /// + /// 男 + /// + Man =1, + /// + /// 女 + /// + Woman = 2, +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/UserHttpClientExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/LINGYUN/Abp/PushPlus/User/UserHttpClientExtensions.cs new file mode 100644 index 000000000..ab52adf4f --- /dev/null +++ b/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 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 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 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(); + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/Microsoft/Extensions/DependencyInjection/IServiceConnectionExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/Microsoft/Extensions/DependencyInjection/IServiceConnectionExtensions.cs new file mode 100644 index 000000000..5a3f2d96c --- /dev/null +++ b/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; + } +} diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/README.md b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/README.md new file mode 100644 index 000000000..d77748b3c --- /dev/null +++ b/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 短信消息通道限制周期(天) diff --git a/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/System/Net/Http/IHttpClientFactoryExtensions.cs b/aspnet-core/modules/pushplus/LINGYUN.Abp.PushPlus/System/Net/Http/IHttpClientFactoryExtensions.cs new file mode 100644 index 000000000..7be814ecc --- /dev/null +++ b/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"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj new file mode 100644 index 000000000..3d29463d8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN.Abp.PushPlus.Tests.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + + false + + + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestBase.cs new file mode 100644 index 000000000..2ad8fdae9 --- /dev/null +++ b/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 +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/AbpPushPlusTestModule.cs new file mode 100644 index 000000000..17c8c3300 --- /dev/null +++ b/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); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSenderTests.cs b/aspnet-core/tests/LINGYUN.Abp.PushPlus.Tests/LINGYUN/Abp/PushPlus/Message/PushPlusMessageSenderTests.cs new file mode 100644 index 000000000..58b3a511d --- /dev/null +++ b/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; + +/// +/// TODO: 接入webhook测试 +/// TODO: 接入企业微信测试 +/// +public class PushPlusMessageSenderTests : AbpPushPlusTestBase +{ + protected IPushPlusMessageSender PushPlusMessageSender { get; } + public PushPlusMessageSenderTests() + { + PushPlusMessageSender = GetRequiredService(); + } + + [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(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs b/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs index e84462a03..3470b0a1a 100644 --- a/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs +++ b/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(featureFunc(feature)); + return Task.FromResult(feature.DefaultValue); } } }