From 0a1b5dd97785d65febcb2f1e000d1175beecf852 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:46:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E9=80=9A=E7=9F=A5=E6=8F=90=E4=BE=9B=E8=80=85?= =?UTF-8?q?;=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E5=85=AC=E5=85=B1token?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....Abp.IdentityServer.WeChatValidator.csproj | 4 + .../AbpIdentityServerWeChatValidatorModule.cs | 6 +- .../WeChatTokenGrantValidator.cs | 21 +++-- .../Http/HttpClientTokenRequestExtensions.cs | 6 +- ...TokenRequest.cs => WeChatOpenIdRequest.cs} | 2 +- ...kenResponse.cs => WeChatOpenIdResponse.cs} | 2 +- ...YUN.Abp.Notifications.WeChat.WeApp.csproj} | 3 +- .../AbpNotificationsWeChatWeAppModule.cs | 30 ++++++ .../AbpWeChatWeAppNotificationOptions.cs | 18 ++++ .../WeApp/IWeChatWeAppNotificationSender.cs | 9 ++ .../WeChatWeAppNotificationPublishProvider.cs | 76 +++++++++++++++ .../WeApp/WeChatWeAppNotificationSender.cs | 93 +++++++++++++++++++ .../WeApp/WeChatWeAppSendNotificationData.cs | 45 +++++++++ .../WeChatNotificationPublishProvider.cs | 25 ----- .../Notifications/INotificationDispatcher.cs | 3 + .../INotificationSubscriptionManager.cs | 59 ++++++++++++ .../Internal/DefaultNotificationDispatcher.cs | 24 ++++- .../NotificationSubscriptionManager.cs | 55 +++++++++++ .../NotificationPublishProvider.cs | 30 +++++- .../UserCreateSendWelcomeEventHandler.cs | 16 +++- .../LINGYUN.Abp.WeChat.Authorization.csproj | 14 +++ .../AbpWeChatAuthorizationModule.cs | 25 +++++ .../WeChat/Authorization/AbpWeChatOptions.cs} | 4 +- .../Authorization/IWeChatTokenProvider.cs | 9 ++ .../Abp/WeChat/Authorization/WeChatToken.cs | 26 ++++++ .../Authorization/WeChatTokenCacheItem.cs | 24 +++++ .../Authorization/WeChatTokenProvider.cs | 88 ++++++++++++++++++ .../Authorization/WeChatTokenRequest.cs | 10 ++ .../Authorization/WeChatTokenResponse.cs | 41 ++++++++ .../HttpClientWeChatTokenRequestExtensions.cs | 26 ++++++ .../Controllers/NotificationController.cs | 5 + ...YUN.Abp.MessageService.HttpApi.Host.csproj | 1 + .../AbpMessageServiceHttpApiHostModule.cs | 2 + 33 files changed, 746 insertions(+), 56 deletions(-) rename aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/{WeChatTokenRequest.cs => WeChatOpenIdRequest.cs} (85%) rename aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/{WeChatTokenResponse.cs => WeChatOpenIdResponse.cs} (96%) rename aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/{LINGYUN.Abp.Notifications.WeChat.csproj => LINGYUN.Abp.Notifications.WeChat.WeApp.csproj} (59%) create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpNotificationsWeChatWeAppModule.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpWeChatWeAppNotificationOptions.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/IWeChatWeAppNotificationSender.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationSender.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppSendNotificationData.cs delete mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeChatNotificationPublishProvider.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSubscriptionManager.cs create mode 100644 aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/NotificationSubscriptionManager.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN.Abp.WeChat.Authorization.csproj create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs rename aspnet-core/{modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpWeChatValidatorOptions.cs => moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs} (55%) create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/IWeChatTokenProvider.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatToken.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenCacheItem.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenProvider.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenRequest.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenResponse.cs create mode 100644 aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/System/Net/Http/HttpClientWeChatTokenRequestExtensions.cs diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj index 307851aa6..fbae27373 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs index 97a959382..a1b4e7c4c 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.IdentityServer.WeChatValidator; +using LINGYUN.Abp.WeChat.Authorization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.IdentityServer; using Volo.Abp.IdentityServer.Localization; @@ -8,7 +9,9 @@ using Volo.Abp.VirtualFileSystem; namespace LINGYUN.Abp.IdentityServer { - [DependsOn(typeof(AbpIdentityServerDomainModule))] + [DependsOn( + typeof(AbpWeChatAuthorizationModule), + typeof(AbpIdentityServerDomainModule))] public class AbpIdentityServerWeChatValidatorModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) @@ -23,7 +26,6 @@ namespace LINGYUN.Abp.IdentityServer { var configuration = context.Services.GetConfiguration(); - Configure(configuration.GetSection("AuthServer:WeChat")); Configure(configuration.GetSection("WeChat:Signature")); context.Services.AddHttpClient(WeChatValidatorConsts.WeChatValidatorClientName, options => diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs index c4583857e..30955d77f 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs @@ -3,6 +3,7 @@ using IdentityServer4.Events; using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Validation; +using LINGYUN.Abp.WeChat.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; @@ -22,7 +23,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator public class WeChatTokenGrantValidator : IExtensionGrantValidator { protected ILogger Logger { get; } - protected AbpWeChatValidatorOptions Options { get; } + protected AbpWeChatOptions Options { get; } protected IHttpClientFactory HttpClientFactory{ get; } protected IEventService EventService { get; } protected IIdentityUserRepository UserRepository { get; } @@ -40,7 +41,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator SignInManager signInManager, IStringLocalizer stringLocalizer, PhoneNumberTokenProvider phoneNumberTokenProvider, - IOptionsSnapshot options, + IOptions options, ILogger logger) { Logger = logger; @@ -77,7 +78,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator return; } var httpClient = HttpClientFactory.CreateClient(WeChatValidatorConsts.WeChatValidatorClientName); - var httpRequest = new WeChatTokenRequest + var httpRequest = new WeChatOpenIdRequest { Code = wechatCode, AppId = Options.AppId, @@ -85,19 +86,19 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator BaseUrl = httpClient.BaseAddress.AbsoluteUri }; - var wechatTokenResponse = await httpClient.RequestWeChatCodeTokenAsync(httpRequest); - if (wechatTokenResponse.IsError) + var wechatOpenIdResponse = await httpClient.RequestWeChatCodeTokenAsync(httpRequest); + if (wechatOpenIdResponse.IsError) { Logger.LogWarning("Authentication failed for token: {0}, reason: invalid token", wechatCode); - Logger.LogWarning("WeChat auth failed, error: {0}", wechatTokenResponse.ErrorMessage); + Logger.LogWarning("WeChat auth failed, error: {0}", wechatOpenIdResponse.ErrorMessage); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, Localizer["InvalidGrant:WeChatTokenInvalid"]); return; } - var currentUser = await UserManager.FindByNameAsync(wechatTokenResponse.OpenId); + var currentUser = await UserManager.FindByNameAsync(wechatOpenIdResponse.OpenId); if(currentUser == null) { - Logger.LogWarning("Invalid grant type: wechat openid: {0} not register", wechatTokenResponse.OpenId); + Logger.LogWarning("Invalid grant type: wechat openid: {0} not register", wechatOpenIdResponse.OpenId); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, Localizer["InvalidGrant:WeChatNotRegister"]); return; @@ -109,9 +110,9 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator { additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, currentUser.TenantId?.ToString())); } - additionalClaims.Add(new Claim(WeChatValidatorConsts.ClaimTypes.OpenId, wechatTokenResponse.OpenId)); + additionalClaims.Add(new Claim(WeChatValidatorConsts.ClaimTypes.OpenId, wechatOpenIdResponse.OpenId)); - await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, wechatTokenResponse.OpenId, null)); + await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, wechatOpenIdResponse.OpenId, null)); context.Result = new GrantValidationResult(sub, WeChatValidatorConsts.AuthenticationMethods.BasedWeChatAuthentication, additionalClaims.ToArray()); } diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/HttpClientTokenRequestExtensions.cs b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/HttpClientTokenRequestExtensions.cs index 169520000..bea6631b8 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/HttpClientTokenRequestExtensions.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/HttpClientTokenRequestExtensions.cs @@ -7,7 +7,7 @@ namespace System.Net.Http { public static class HttpClientTokenRequestExtensions { - public static async Task RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatTokenRequest request, CancellationToken cancellationToken = default) + public static async Task RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatOpenIdRequest request, CancellationToken cancellationToken = default) { var getResuestUrlBuiilder = new StringBuilder(); getResuestUrlBuiilder.Append(request.BaseUrl); @@ -24,9 +24,9 @@ namespace System.Net.Http } catch (Exception ex) { - return ProtocolResponse.FromException(ex); + return ProtocolResponse.FromException(ex); } - return await ProtocolResponse.FromHttpResponseAsync(httpResponse).ConfigureAwait(false); + return await ProtocolResponse.FromHttpResponseAsync(httpResponse).ConfigureAwait(false); } } } diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenRequest.cs b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdRequest.cs similarity index 85% rename from aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenRequest.cs rename to aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdRequest.cs index 14088f187..5dddab29e 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenRequest.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdRequest.cs @@ -1,6 +1,6 @@ namespace System.Net.Http { - public class WeChatTokenRequest + public class WeChatOpenIdRequest { public string BaseUrl { get; set; } public string AppId { get; set; } diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenResponse.cs b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdResponse.cs similarity index 96% rename from aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenResponse.cs rename to aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdResponse.cs index 983a0bc4b..8e13e7d83 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatTokenResponse.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Net/Http/WeChatOpenIdResponse.cs @@ -2,7 +2,7 @@ namespace System.Net.Http { - public class WeChatTokenResponse : ProtocolResponse + public class WeChatOpenIdResponse : ProtocolResponse { /// /// 用户唯一标识 diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.csproj b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.WeApp.csproj similarity index 59% rename from aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.csproj rename to aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.WeApp.csproj index 4ed83b791..56fffe29c 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.csproj +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN.Abp.Notifications.WeChat.WeApp.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -6,6 +6,7 @@ + diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpNotificationsWeChatWeAppModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpNotificationsWeChatWeAppModule.cs new file mode 100644 index 000000000..146da9d22 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpNotificationsWeChatWeAppModule.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.WeChat.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Polly; +using System; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + [DependsOn( + typeof(AbpWeChatAuthorizationModule), + typeof(AbpNotificationModule))] + public class AbpNotificationsWeChatWeAppModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + Configure(configuration.GetSection("Notifications:WeChat:WeApp")); + + // TODO:是否有必要启用重试机制? + context.Services.AddHttpClient(WeChatWeAppNotificationSender.SendNotificationClientName) + .AddTransientHttpErrorPolicy(builder => + builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); + + Configure(options => + { + options.PublishProviders.Add(); + }); + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpWeChatWeAppNotificationOptions.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpWeChatWeAppNotificationOptions.cs new file mode 100644 index 000000000..17f20e7be --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/AbpWeChatWeAppNotificationOptions.cs @@ -0,0 +1,18 @@ +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + public class AbpWeChatWeAppNotificationOptions + { + /// + /// 默认小程序模板 + /// + public string DefaultTemplateId { get; set; } + /// + /// 默认跳转小程序类型 + /// + public string DefaultWeAppState { get; set; } = "developer"; + /// + /// 默认小程序语言 + /// + public string DefaultWeAppLanguage { get; set; } = "zh_CN"; + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/IWeChatWeAppNotificationSender.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/IWeChatWeAppNotificationSender.cs new file mode 100644 index 000000000..de5d8c500 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/IWeChatWeAppNotificationSender.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + public interface IWeChatWeAppNotificationSender + { + Task SendAsync(WeChatWeAppSendNotificationData notificationData); + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs new file mode 100644 index 000000000..837e8c7b2 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + /// + /// 微信小程序消息推送提供者 + /// + public class WeChatWeAppNotificationPublishProvider : NotificationPublishProvider + { + public override string Name => "WeChat.WeApp"; + + protected IWeChatWeAppNotificationSender NotificationSender { get; } + protected AbpWeChatWeAppNotificationOptions Options { get; } + public WeChatWeAppNotificationPublishProvider( + IServiceProvider serviceProvider, + IWeChatWeAppNotificationSender notificationSender, + IOptions options) + : base(serviceProvider) + { + Options = options.Value; + NotificationSender = notificationSender; + } + + public override async Task PublishAsync(NotificationInfo notification, IEnumerable identifiers) + { + // step1 默认微信openid绑定的就是username, + // 如果不是,需要自行处理openid获取逻辑 + + // step2 调用微信消息推送接口 + foreach (var identifier in identifiers) + { + await SendWeChatTemplateMessagAsync(notification, identifier); + } + } + + protected virtual async Task SendWeChatTemplateMessagAsync(NotificationInfo notification, UserIdentifier identifier) + { + var templateId = GetOrDefaultTemplateId(notification.Data); + Logger.LogDebug($"Get wechat weapp template id: {templateId}"); + + var redirect = GetOrDefault(notification.Data, "RedirectPage", ""); + Logger.LogDebug($"Get wechat weapp redirect page: {redirect}"); + + var weAppState = GetOrDefault(notification.Data, "WeAppState", Options.DefaultWeAppState); + Logger.LogDebug($"Get wechat weapp state: {weAppState}"); + + var weAppLang = GetOrDefault(notification.Data, "WeAppLanguage", Options.DefaultWeAppLanguage); + Logger.LogDebug($"Get wechat weapp language: {weAppLang}"); + + var weChatWeAppNotificationData = new WeChatWeAppSendNotificationData(identifier.UserName, + templateId, redirect, weAppState, weAppLang); + + Logger.LogDebug($"Sending wechat weapp notification: {notification.Name}"); + // 发送小程序订阅消息 + await NotificationSender.SendAsync(weChatWeAppNotificationData); + } + + protected string GetOrDefaultTemplateId(NotificationData data) + { + return GetOrDefault(data, "TemplateId", Options.DefaultTemplateId); + } + + protected string GetOrDefault(NotificationData data, string key, string defaultValue) + { + if (data.Properties.TryGetValue(key, out object value)) + { + return value.ToString(); + } + return defaultValue; + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationSender.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationSender.cs new file mode 100644 index 000000000..ba912aaa4 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationSender.cs @@ -0,0 +1,93 @@ +using LINGYUN.Abp.WeChat.Authorization; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + public class WeChatWeAppNotificationSender : IWeChatWeAppNotificationSender, ITransientDependency + { + public const string SendNotificationClientName = "WeChatWeAppSendNotificationClient"; + protected IHttpClientFactory HttpClientFactory { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IWeChatTokenProvider WeChatTokenProvider { get; } + public WeChatWeAppNotificationSender( + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IWeChatTokenProvider weChatTokenProvider) + { + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + WeChatTokenProvider = weChatTokenProvider; + } + + public virtual async Task SendAsync(WeChatWeAppSendNotificationData notificationData) + { + var weChatToken = await WeChatTokenProvider.GetTokenAsync(); + var requestParamters = new Dictionary + { + { "access_token", weChatToken.AccessToken } + }; + var weChatSendNotificationUrl = "https://api.weixin.qq.com"; + var weChatSendNotificationPath = "/cgi-bin/message/subscribe/send"; + var requestUrl = BuildRequestUrl(weChatSendNotificationUrl, weChatSendNotificationPath, requestParamters); + var responseContent = await MakeRequestAndGetResultAsync(requestUrl, notificationData); + var weChatSenNotificationResponse = JsonSerializer.Deserialize(responseContent); + weChatSenNotificationResponse.ThrowIfNotSuccess(); + } + protected virtual async Task MakeRequestAndGetResultAsync(string url, WeChatWeAppSendNotificationData notificationData) + { + var client = HttpClientFactory.CreateClient(SendNotificationClientName); + var requestContent = new StringContent(JsonSerializer.Serialize(notificationData)); + var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = requestContent + }; + + var response = await client.SendAsync(requestMessage); + if (!response.IsSuccessStatusCode) + { + throw new AbpException($"Baidu http request service returns error! HttpStatusCode: {response.StatusCode}, ReasonPhrase: {response.ReasonPhrase}"); + } + var resultContent = await response.Content.ReadAsStringAsync(); + + return resultContent; + } + + protected virtual string BuildRequestUrl(string uri, string path, IDictionary paramters) + { + var requestUrlBuilder = new StringBuilder(128); + requestUrlBuilder.Append(uri); + requestUrlBuilder.Append(path).Append("?"); + foreach (var paramter in paramters) + { + requestUrlBuilder.AppendFormat("{0}={1}", paramter.Key, paramter.Value); + requestUrlBuilder.Append("&"); + } + requestUrlBuilder.Remove(requestUrlBuilder.Length - 1, 1); + return requestUrlBuilder.ToString(); + } + } + + public class WeChatSendNotificationResponse + { + [JsonProperty("errcode")] + public int ErrorCode { get; set; } + + [JsonProperty("errmsg")] + public string ErrorMessage { get; set; } + + public void ThrowIfNotSuccess() + { + if (ErrorCode != 0) + { + throw new AbpException($"Send wechat weapp notification error:{ErrorMessage}"); + } + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppSendNotificationData.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppSendNotificationData.cs new file mode 100644 index 000000000..27852d822 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppSendNotificationData.cs @@ -0,0 +1,45 @@ +#pragma warning disable IDE1006 // 禁止编译器提示 +namespace LINGYUN.Abp.Notifications.WeChat.WeApp +{ + public class WeChatWeAppSendNotificationData + { + /// + /// 接收者(用户)的 openid + /// + public string touser { get; set; } + /// + /// 所需下发的订阅模板id + /// + public string template_id { get; set; } + /// + /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。 + /// 支持带参数,(示例index?foo=bar)。 + /// 该字段不填则模板无跳转 + /// + public string page { get; set; } + /// + /// 跳转小程序类型: + /// developer为开发版;trial为体验版;formal为正式版; + /// 默认为正式版 + /// + public string miniprogram_state { get; set; } + /// + /// 进入小程序查看”的语言类型, + /// 支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文), + /// 默认为zh_CN + /// + public string lang { get; set; } + + public WeChatWeAppSendNotificationData() { } + public WeChatWeAppSendNotificationData(string openId, string templateId, string redirectPage = "", + string state = "formal", string miniLang = "zh_CN") + { + touser = openId; + template_id = templateId; + page = redirectPage; + miniprogram_state = state; + lang = miniLang; + } + } +} +#pragma warning restore IDE1006 diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeChatNotificationPublishProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeChatNotificationPublishProvider.cs deleted file mode 100644 index f98ddfcc6..000000000 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeChatNotificationPublishProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Notifications.WeChat -{ - public class WeChatNotificationPublishProvider : NotificationPublishProvider - { - public override string Name => "WeChat"; - - public WeChatNotificationPublishProvider( - IServiceProvider serviceProvider) - : base(serviceProvider) - { - - } - - public override async Task PublishAsync(NotificationInfo notification, IEnumerable identifiers) - { - // step1 默认微信openid绑定的就是username,如果不是,那就根据userid去获取 - - // step2 调用微信消息推送接口 - } - } -} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs index 2ed1301db..5e8e3b6da 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs @@ -3,6 +3,9 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.Notifications { + /// + /// 通知发送者接口 + /// public interface INotificationDispatcher { /// diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSubscriptionManager.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSubscriptionManager.cs new file mode 100644 index 000000000..f1d820598 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSubscriptionManager.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications +{ + /// + /// 通知订阅管理器 + /// + public interface INotificationSubscriptionManager + { + /// + /// 是否已订阅 + /// + /// 租户 + /// 用户标识 + /// 通知名称 + /// + Task IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName); + /// + /// 订阅通知 + /// + /// 租户 + /// 用户标识 + /// 通知名称 + /// + Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); + /// + /// 订阅通知 + /// + /// 租户 + /// 用户标识列表 + /// 通知名称 + /// + Task SubscribeAsync(Guid? tenantId, IEnumerable identifiers, string notificationName); + /// + /// 取消订阅 + /// + /// 租户 + /// 用户标识 + /// 通知名称 + /// + Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); + /// + /// 获取通知被订阅用户列表 + /// + /// 租户 + /// 通知名称 + /// + Task> GetSubscriptionsAsync(Guid? tenantId, string notificationName); + /// + /// 获取用户订阅列表 + /// + /// 租户 + /// 用户标识 + /// + Task> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId); + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/DefaultNotificationDispatcher.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/DefaultNotificationDispatcher.cs index 6b5bfa6e0..36fea5853 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/DefaultNotificationDispatcher.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/DefaultNotificationDispatcher.cs @@ -30,7 +30,14 @@ namespace LINGYUN.Abp.Notifications.Internal NotificationSeverity notificationSeverity = NotificationSeverity.Info) { // 获取自定义的通知 - var defineNotification = _notificationDefinitionManager.Get(notificationName); + var defineNotification = _notificationDefinitionManager.GetOrNull(notificationName); + + // 没有定义的通知,应该也要能发布、订阅, + // 比如订单之类的,是以订单编号为通知名称,这是动态的,没法自定义 + if(defineNotification == null) + { + defineNotification = new NotificationDefinition(notificationName); + } var notificationInfo = new NotificationInfo { @@ -75,31 +82,38 @@ namespace LINGYUN.Abp.Notifications.Internal protected async Task PublishFromProvidersAsync(IEnumerable providers, NotificationInfo notificationInfo) { + Logger.LogDebug($"Persistent notification {notificationInfo.Name}"); // 持久化通知 await _notificationStore.InsertNotificationAsync(notificationInfo); + Logger.LogDebug($"Gets a list of user subscriptions {notificationInfo.Name}"); // 获取用户订阅列表 var userSubscriptions = await _notificationStore.GetSubscriptionsAsync(notificationInfo.TenantId, notificationInfo.Name); + Logger.LogDebug($"Persistent user notifications {notificationInfo.Name}"); // 持久化用户通知 var subscriptionUserIdentifiers = userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName)); await _notificationStore.InsertUserNotificationsAsync(notificationInfo, subscriptionUserIdentifiers.Select(u => u.UserId)); - // 发送通知 + // 发布通知 foreach (var provider in providers) { try { + Logger.LogDebug($"Sending notification with provider {provider.Name}"); + await provider.PublishAsync(notificationInfo, subscriptionUserIdentifiers); + + Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful"); } catch(Exception ex) { - Logger.LogWarning("Send notification error with provider {0}", provider.Name); - Logger.LogWarning("Error message:{0}", ex.Message); + Logger.LogWarning($"Send notification error with provider {provider.Name}"); + Logger.LogWarning($"Error message:{ex.Message}"); - Logger.LogTrace(ex, "Send notification error with provider {0}", provider.Name); + Logger.LogTrace(ex, $"Send notification error with provider { provider.Name}"); } } } diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/NotificationSubscriptionManager.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/NotificationSubscriptionManager.cs new file mode 100644 index 000000000..508b400ef --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/Internal/NotificationSubscriptionManager.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Notifications.Internal +{ + internal class NotificationSubscriptionManager : INotificationSubscriptionManager, ITransientDependency + { + private readonly INotificationStore _store; + + public NotificationSubscriptionManager( + INotificationStore store) + { + _store = store; + } + + public virtual async Task> GetSubscriptionsAsync(Guid? tenantId, string notificationName) + { + return await _store.GetSubscriptionsAsync(tenantId, notificationName); + } + + public virtual async Task> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId) + { + return await _store.GetUserSubscriptionsAsync(tenantId, userId); + } + + public virtual async Task IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName) + { + return await _store.IsSubscribedAsync(tenantId, userId, notificationName); + } + + public virtual async Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) + { + if (await IsSubscribedAsync(tenantId, identifier.UserId, notificationName)) + { + return; + } + await _store.InsertUserSubscriptionAsync(tenantId, identifier, notificationName); + } + + public virtual async Task SubscribeAsync(Guid? tenantId, IEnumerable identifiers, string notificationName) + { + foreach(var identifier in identifiers) + { + await SubscribeAsync(tenantId, identifier, notificationName); + } + } + + public virtual async Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) + { + await _store.DeleteUserSubscriptionAsync(tenantId, identifier.UserId, notificationName); + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationPublishProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationPublishProvider.cs index 1f8a7a6a0..c2f4fda24 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationPublishProvider.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationPublishProvider.cs @@ -1,4 +1,7 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -11,6 +14,31 @@ namespace LINGYUN.Abp.Notifications protected IServiceProvider ServiceProvider { get; } + protected readonly object ServiceProviderLock = new object(); + + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; + + protected ILogger Logger => _lazyLogger.Value; + private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); + + + protected TService LazyGetRequiredService(ref TService reference) + { + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = ServiceProvider.GetRequiredService(); + } + } + } + + return reference; + } + protected NotificationPublishProvider(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; diff --git a/aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserCreateSendWelcomeEventHandler.cs b/aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserCreateSendWelcomeEventHandler.cs index 63fef383c..d676d9e38 100644 --- a/aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserCreateSendWelcomeEventHandler.cs +++ b/aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserCreateSendWelcomeEventHandler.cs @@ -18,24 +18,26 @@ namespace LINGYUN.Abp.MessageService.EventBus { private readonly ISettingProvider _settingProvider; private readonly IStringLocalizer _stringLocalizer; - private readonly INotificationStore _notificationStore; + private readonly INotificationDispatcher _notificationDispatcher; + private readonly INotificationSubscriptionManager _notificationSubscriptionManager; // 需要模拟用户令牌 // 是否有必要 // private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; public UserCreateSendWelcomeEventHandler( ISettingProvider settingProvider, - INotificationStore notificationStore, INotificationDispatcher notificationDispatcher, - IStringLocalizer stringLocalizer + IStringLocalizer stringLocalizer, + INotificationSubscriptionManager notificationSubscriptionManager //ICurrentPrincipalAccessor currentPrincipalAccessor ) { _settingProvider = settingProvider; _stringLocalizer = stringLocalizer; - _notificationStore = notificationStore; + _notificationDispatcher = notificationDispatcher; + _notificationSubscriptionManager = notificationSubscriptionManager; //_currentPrincipalAccessor = currentPrincipalAccessor; } @@ -53,9 +55,13 @@ namespace LINGYUN.Abp.MessageService.EventBus { var userIdentifer = new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName); // 订阅用户欢迎消息 - await _notificationStore.InsertUserSubscriptionAsync(eventData.Entity.TenantId, + await _notificationSubscriptionManager.SubscribeAsync(eventData.Entity.TenantId, userIdentifer, UserNotificationNames.WelcomeToApplication); + // Store未检查已订阅 + //await _notificationStore.InsertUserSubscriptionAsync(eventData.Entity.TenantId, + // userIdentifer, UserNotificationNames.WelcomeToApplication); + var userWelcomeNotifictionData = new NotificationData(); // 换成用户名称,而不是用户名 diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN.Abp.WeChat.Authorization.csproj b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN.Abp.WeChat.Authorization.csproj new file mode 100644 index 000000000..95b04ed53 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN.Abp.WeChat.Authorization.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + + + + + + + + + + diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs new file mode 100644 index 000000000..e6bf4c160 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Polly; +using System; +using Volo.Abp.Caching; +using Volo.Abp.Json; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.WeChat.Authorization +{ + [DependsOn(typeof(AbpJsonModule), typeof(AbpCachingModule))] + public class AbpWeChatAuthorizationModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + Configure(configuration.GetSection("WeChat:Auth")); + + context.Services.AddHttpClient("WeChatTokenProviderClient", options => + { + options.BaseAddress = new Uri("https://api.weixin.qq.com/cgi-bin/token"); + }).AddTransientHttpErrorPolicy(builder => + builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpWeChatValidatorOptions.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs similarity index 55% rename from aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpWeChatValidatorOptions.cs rename to aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs index 32cf49f46..3c4174d9f 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpWeChatValidatorOptions.cs +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs @@ -1,6 +1,6 @@ -namespace LINGYUN.Abp.IdentityServer +namespace LINGYUN.Abp.WeChat.Authorization { - public class AbpWeChatValidatorOptions + public class AbpWeChatOptions { public string AppId { get; set; } public string AppSecret { get; set; } diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/IWeChatTokenProvider.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/IWeChatTokenProvider.cs new file mode 100644 index 000000000..dddf69239 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/IWeChatTokenProvider.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Authorization +{ + public interface IWeChatTokenProvider + { + Task GetTokenAsync(); + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatToken.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatToken.cs new file mode 100644 index 000000000..e09902f0f --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatToken.cs @@ -0,0 +1,26 @@ +namespace LINGYUN.Abp.WeChat.Authorization +{ + /// + /// 微信令牌 + /// + public class WeChatToken + { + /// + /// 访问令牌 + /// + public string AccessToken { get; set; } + /// + /// 过期时间,单位(s) + /// + public int ExpiresIn { get; set; } + public WeChatToken() + { + + } + public WeChatToken(string token, int expiresIn) + { + AccessToken = token; + ExpiresIn = expiresIn; + } + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenCacheItem.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenCacheItem.cs new file mode 100644 index 000000000..e0a2d27ee --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenCacheItem.cs @@ -0,0 +1,24 @@ +namespace LINGYUN.Abp.WeChat.Authorization +{ + public class WeChatTokenCacheItem + { + public string AppId { get; set; } + + public WeChatToken WeChatToken { get; set; } + public WeChatTokenCacheItem() + { + + } + + public WeChatTokenCacheItem(string appId, WeChatToken weChatToken) + { + AppId = appId; + WeChatToken = weChatToken; + } + + public static string CalculateCacheKey(string provider, string appId) + { + return "p:" + provider + ",o:" + appId; + } + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenProvider.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenProvider.cs new file mode 100644 index 000000000..3471461e3 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenProvider.cs @@ -0,0 +1,88 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.WeChat.Authorization +{ + public class WeChatTokenProvider : IWeChatTokenProvider, ISingletonDependency + { + public ILogger Logger { get; set; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IDistributedCache Cache { get; } + protected AbpWeChatOptions Options { get; } + public WeChatTokenProvider( + IJsonSerializer jsonSerializer, + IHttpClientFactory httpClientFactory, + IOptions options, + IDistributedCache cache) + { + JsonSerializer = jsonSerializer; + HttpClientFactory = httpClientFactory; + + Cache = cache; + Options = options.Value; + + Logger = NullLogger.Instance; + } + + public virtual async Task GetTokenAsync() + { + return (await GetCacheItemAsync("WeChatToken", Options.AppId)).WeChatToken; + } + + protected virtual async Task GetCacheItemAsync(string provider, string appId) + { + var cacheKey = WeChatTokenCacheItem.CalculateCacheKey(provider, appId); + + Logger.LogDebug($"WeChatTokenProvider.GetCacheItemAsync: {cacheKey}"); + + var cacheItem = await Cache.GetAsync(cacheKey); + + 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.CreateClient("WeChatTokenProviderClient"); + + var request = new WeChatTokenRequest + { + BaseUrl = client.BaseAddress.AbsoluteUri, + AppSecret = Options.AppSecret, + AppId = Options.AppId, + GrantType = "client_credential" + }; + + var response = await client.RequestWeChatCodeTokenAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + var weChatTokenResponse = JsonSerializer.Deserialize(responseContent); + var weChatToken = weChatTokenResponse.ToWeChatToken(); + cacheItem = new WeChatTokenCacheItem(appId, weChatToken); + + Logger.LogDebug($"Setting the cache item: {cacheKey}"); + + var cacheOptions = new DistributedCacheEntryOptions + { + // 设置绝对过期时间为Token有效期剩余的二分钟 + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(weChatToken.ExpiresIn - 120) + }; + + await Cache.SetAsync(cacheKey, cacheItem, cacheOptions); + + Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); + + return cacheItem; + } + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenRequest.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenRequest.cs new file mode 100644 index 000000000..3259b378d --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenRequest.cs @@ -0,0 +1,10 @@ +namespace LINGYUN.Abp.WeChat.Authorization +{ + public class WeChatTokenRequest + { + public string BaseUrl { get; set; } + public string GrantType { get; set; } + public string AppId { get; set; } + public string AppSecret { get; set; } + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenResponse.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenResponse.cs new file mode 100644 index 000000000..e6be63b80 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatTokenResponse.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Authorization +{ + /// + /// 微信访问令牌返回对象 + /// + public class WeChatTokenResponse + { + /// + /// 错误码 + /// + [JsonProperty("errcode")] + public int ErrorCode { get; set; } + /// + /// 错误消息 + /// + [JsonProperty("errmsg")] + public string ErrorMessage { get; set; } + /// + /// 访问令牌 + /// + [JsonProperty("access_token")] + public string AccessToken { get; set; } + /// + /// 过期时间,单位(s) + /// + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + public WeChatToken ToWeChatToken() + { + if(ErrorCode != 0) + { + throw new AbpException(ErrorMessage); + } + return new WeChatToken(AccessToken, ExpiresIn); + } + } +} diff --git a/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/System/Net/Http/HttpClientWeChatTokenRequestExtensions.cs b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/System/Net/Http/HttpClientWeChatTokenRequestExtensions.cs new file mode 100644 index 000000000..08cb5cda3 --- /dev/null +++ b/aspnet-core/moudles/common/LINGYUN.Abp.WeChat.Authorization/System/Net/Http/HttpClientWeChatTokenRequestExtensions.cs @@ -0,0 +1,26 @@ +using LINGYUN.Abp.WeChat.Authorization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http +{ + public static class HttpClientWeChatTokenRequestExtensions + { + public static async Task RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatTokenRequest request, CancellationToken cancellationToken = default) + { + var getResuestUrlBuilder = new StringBuilder(); + getResuestUrlBuilder.Append(request.BaseUrl); + getResuestUrlBuilder.Append("?grant_type=client_credential"); + getResuestUrlBuilder.AppendFormat("&appid={0}", request.AppId); + getResuestUrlBuilder.AppendFormat("&secret={0}", request.AppSecret); + + var getRequest = new HttpRequestMessage(HttpMethod.Get, getResuestUrlBuilder.ToString()); + HttpResponseMessage httpResponse; + + httpResponse = await client.SendAsync(getRequest, cancellationToken).ConfigureAwait(false); + + return httpResponse; + } + } +} diff --git a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Controllers/NotificationController.cs b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Controllers/NotificationController.cs index ab30847b9..476873529 100644 --- a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Controllers/NotificationController.cs +++ b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Controllers/NotificationController.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.Notifications; using Microsoft.AspNetCore.Mvc; using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.AspNetCore.Mvc; @@ -26,6 +27,8 @@ namespace LINGYUN.Abp.MessageService.Controllers notificationData.Properties["datetime"] = Clock.Now; notificationData.Properties["severity"] = notification.Severity; + notificationData.Properties.AddIfNotContains(notification.Data); + await _notificationDispatcher.DispatchAsync("TestApplicationNotofication", notificationData, notificationSeverity: notification.Severity); @@ -38,7 +41,9 @@ namespace LINGYUN.Abp.MessageService.Controllers public Guid UserId { get; set; } public string Title { get; set; } public string Message { get; set; } + public Dictionary Data { get; set; } = new Dictionary(); public NotificationSeverity Severity { get; set; } = NotificationSeverity.Success; + } public class TestApplicationNotificationData : NotificationData diff --git a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj index 1f29d704e..95c765ebd 100644 --- a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj +++ b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj @@ -35,6 +35,7 @@ + diff --git a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN/Abp/MessageService/AbpMessageServiceHttpApiHostModule.cs b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN/Abp/MessageService/AbpMessageServiceHttpApiHostModule.cs index d7f763c9f..7d4e7e8bc 100644 --- a/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN/Abp/MessageService/AbpMessageServiceHttpApiHostModule.cs +++ b/aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN/Abp/MessageService/AbpMessageServiceHttpApiHostModule.cs @@ -5,6 +5,7 @@ using LINGYUN.Abp.IM.SignalR; using LINGYUN.Abp.MessageService.EntityFrameworkCore; using LINGYUN.Abp.MessageService.MultiTenancy; using LINGYUN.Abp.Notifications.SignalR; +using LINGYUN.Abp.Notifications.WeChat.WeApp; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -40,6 +41,7 @@ namespace LINGYUN.Abp.MessageService typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpIMSignalRModule), typeof(AbpNotificationsSignalRModule), + typeof(AbpNotificationsWeChatWeAppModule), typeof(AbpCAPEventBusModule), typeof(AbpAutofacModule) )]