85 changed files with 1205 additions and 997 deletions
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<Description>阿里云SDK基础框架</Description> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINYUN\Abp\Aliyun\Localization\Resources\en.json" /> |
||||
|
<None Remove="LINYUN\Abp\Aliyun\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINYUN\Abp\Aliyun\Localization\Resources\en.json" /> |
||||
|
<EmbeddedResource Include="LINYUN\Abp\Aliyun\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="aliyun-net-sdk-core" Version="1.5.8" /> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="3.3.0" /> |
||||
|
<PackageReference Include="Volo.Abp.Json" Version="3.3.0" /> |
||||
|
<PackageReference Include="Volo.Abp.Settings" Version="3.3.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,29 @@ |
|||||
|
using LINYUN.Abp.Aliyun.Localization; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Aliyun |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpJsonModule), |
||||
|
typeof(AbpLocalizationModule))] |
||||
|
public class AbpAliyunCloudModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpAliyunCloudModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<AliyunResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/Aliyun/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINYUN.Abp.Aliyun.Localization |
||||
|
{ |
||||
|
[LocalizationResourceName("Aliyun")] |
||||
|
public class AliyunResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
namespace LINYUN.Abp.Aliyun.Settings |
||||
|
{ |
||||
|
public static class AliyunSettingNames |
||||
|
{ |
||||
|
public const string Prefix = "Abp.Aliyun"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 认证方式
|
||||
|
/// </summary>
|
||||
|
public class Authorization |
||||
|
{ |
||||
|
public const string Prefix = AliyunSettingNames.Prefix + ".Authorization"; |
||||
|
|
||||
|
public const string AccessKeyId = Prefix + ".AccessKeyId"; |
||||
|
|
||||
|
public const string AccessKeySecret = Prefix + ".AccessKeySecret"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<Description>腾讯云SDK基础框架</Description> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINYUN\Abp\Tencent\Localization\Resources\en.json" /> |
||||
|
<None Remove="LINYUN\Abp\Tencent\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINYUN\Abp\Tencent\Localization\Resources\en.json" /> |
||||
|
<EmbeddedResource Include="LINYUN\Abp\Tencent\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="3.3.0" /> |
||||
|
<PackageReference Include="Volo.Abp.Json" Version="3.3.0" /> |
||||
|
<PackageReference Include="Volo.Abp.Settings" Version="3.3.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,29 @@ |
|||||
|
using LINYUN.Abp.Tencent.Localization; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Tencent |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpJsonModule), |
||||
|
typeof(AbpLocalizationModule))] |
||||
|
public class AbpTencentCloudModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpTencentCloudModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<TencentResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/Tencent/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINYUN.Abp.Tencent.Localization |
||||
|
{ |
||||
|
[LocalizationResourceName("Tencent")] |
||||
|
public class TencentResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
namespace LINGYUN.Abp.IM.SignalR |
||||
|
{ |
||||
|
public class AbpIMSignalROptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 自定义的客户端接收消息方法名称
|
||||
|
/// </summary>
|
||||
|
public string GetChatMessageMethod { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户上线接收方法名称
|
||||
|
/// </summary>
|
||||
|
public string UserOnlineMethod { get; set; } |
||||
|
public AbpIMSignalROptions() |
||||
|
{ |
||||
|
GetChatMessageMethod = "getChatMessage"; |
||||
|
UserOnlineMethod = "onUserOnlined"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.SignalR |
||||
|
{ |
||||
|
public class AbpNotificationsSignalROptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 自定义的客户端订阅通知方法名称
|
||||
|
/// </summary>
|
||||
|
public string MethodName { get; set; } |
||||
|
|
||||
|
public AbpNotificationsSignalROptions() |
||||
|
{ |
||||
|
MethodName = "getNotification"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,19 +0,0 @@ |
|||||
using Volo.Abp.Modularity; |
|
||||
using Volo.Abp.Sms; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.Sms |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpNotificationModule), |
|
||||
typeof(AbpSmsModule))] |
|
||||
public class AbpNotificationsSmsModule : AbpModule |
|
||||
{ |
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
Configure<AbpNotificationOptions>(options => |
|
||||
{ |
|
||||
options.PublishProviders.Add<SmsNotificationPublishProvider>(); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,31 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Sms; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationModule), |
||||
|
typeof(AbpSmsModule))] |
||||
|
public class AbpNotificationsSmsModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpNotificationsSmsOptions>(options => |
||||
|
{ |
||||
|
context.Services.ExecutePreConfiguredActions(options); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
options.PublishProviders.Add<SmsNotificationPublishProvider>(); |
||||
|
|
||||
|
var smsOptions = context.Services.ExecutePreConfiguredActions<AbpNotificationsSmsOptions>(); |
||||
|
options.NotificationDataMappings |
||||
|
.MappingDefault( |
||||
|
SmsNotificationPublishProvider.ProviderName, |
||||
|
data => NotificationData.ToStandardData(smsOptions.TemplateParamsPrefix, data)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
namespace LINGYUN.Abp.Notifications.Sms |
namespace LINGYUN.Abp.Notifications.Sms |
||||
{ |
{ |
||||
public class NotificationSmsOptions |
public class AbpNotificationsSmsOptions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// 短信模板变量前缀
|
/// 短信模板变量前缀
|
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 短信通知发送接口
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 重写实现自定义的短信消息处理
|
||||
|
/// </remarks>
|
||||
|
public interface ISmsNotificationSender |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification">通知数据</param>
|
||||
|
/// <param name="phoneNumbers">手机号列表,多个手机号通过,分隔</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SendAsync(NotificationInfo notification, string phoneNumbers); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Sms; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 短信通知的默认实现者
|
||||
|
/// </summary>
|
||||
|
public class SmsNotificationSender : ISmsNotificationSender, ITransientDependency |
||||
|
{ |
||||
|
public ILogger Logger { protected get; set; } |
||||
|
protected ISmsSender SmsSender { get; } |
||||
|
|
||||
|
public SmsNotificationSender(ISmsSender smsSender) |
||||
|
{ |
||||
|
SmsSender = smsSender; |
||||
|
|
||||
|
Logger = NullLogger<SmsNotificationSender>.Instance; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 发送通知
|
||||
|
/// </summary>
|
||||
|
/// <param name="notification"></param>
|
||||
|
/// <param name="phoneNumbers"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public virtual async Task SendAsync(NotificationInfo notification, string phoneNumbers) |
||||
|
{ |
||||
|
var templateCode = notification.Data.TryGetData("TemplateCode"); |
||||
|
if (templateCode == null) |
||||
|
{ |
||||
|
Logger.LogWarning("sms template code is empty, can not send sms message!"); |
||||
|
return; |
||||
|
} |
||||
|
var message = new SmsMessage(phoneNumbers, "SmsNotification"); |
||||
|
|
||||
|
// TODO: 后期增强功能,增加短信模板、通知模板功能
|
||||
|
message.Properties.Add("TemplateCode", templateCode); |
||||
|
message.Properties.Add("SignName", notification.Data.TryGetData("SignName")); |
||||
|
|
||||
|
await SmsSender.SendAsync(message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
# LINGYUN.Abp.Notifications.Sms |
||||
|
|
||||
|
通知发布提供程序的短信实现 |
||||
|
|
||||
|
大部分重写的模块都和官方模块名称保持一致,通过命名空间区分,主要是只改写了一小部分或者增加额外的功能 |
||||
|
如果大部分模块代码都重写,或者完全就是扩展模块,才会定义自己的名字 |
||||
|
|
||||
|
#### 注意 |
||||
|
|
||||
|
自定义的发送方法可以通过实现 ##ISmsNotificationSender## 接口或重写 ##SmsNotificationSender## 即可 |
||||
|
|
||||
|
内置了通知数据 NotificationDataMappings 方法 |
||||
|
可通过 NotificationDataMappings.MappingAll(SmsNotificationPublishProvider.ProviderName, Func<NotificationData, NotificationData> func) 来自定义规则 |
||||
|
|
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
* 此配置项将在下一个短信相关大版本移除 |
||||
|
|
||||
|
```json |
||||
|
|
||||
|
{ |
||||
|
"Notifications": { |
||||
|
"Sms": { |
||||
|
"TemplateParamsPrefix": "短信模板变量前缀" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpNotificationsSmsModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public static class NotificationDataMappingDictionaryItemExtensions |
||||
|
{ |
||||
|
public static NotificationDataMappingDictionaryItem GetOrNullDefault( |
||||
|
this IEnumerable<NotificationDataMappingDictionaryItem> items, |
||||
|
string name) |
||||
|
{ |
||||
|
var item = items.FirstOrDefault(i => i.Name.Equals(name)); |
||||
|
if (item == null) |
||||
|
{ |
||||
|
return items.FirstOrDefault(i => i.Name.Equals(NotificationDataMappingDictionary.DefaultKey)); |
||||
|
} |
||||
|
return item; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,11 +1,7 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
<Import Project="..\..\..\common.props" /> |
|
||||
|
|
||||
<PropertyGroup> |
<PropertyGroup> |
||||
<TargetFramework>netstandard2.0</TargetFramework> |
<TargetFramework>netstandard2.0</TargetFramework> |
||||
<RootNamespace /> |
|
||||
<Description>通知接口的微信小程序发布者实现</Description> |
|
||||
</PropertyGroup> |
</PropertyGroup> |
||||
|
|
||||
<ItemGroup> |
<ItemGroup> |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.WeChat.MiniProgram; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpWeChatMiniProgramModule), |
||||
|
typeof(AbpNotificationModule))] |
||||
|
public class AbpNotificationsWeChatMiniProgramModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpNotificationsWeChatMiniProgramOptions>(options => |
||||
|
{ |
||||
|
context.Services.ExecutePreConfiguredActions(options); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
options.PublishProviders.Add<WeChatMiniProgramNotificationPublishProvider>(); |
||||
|
|
||||
|
var wechatOptions = context.Services.ExecutePreConfiguredActions<AbpNotificationsWeChatMiniProgramOptions>(); |
||||
|
options.NotificationDataMappings |
||||
|
.MappingDefault(WeChatMiniProgramNotificationPublishProvider.ProviderName, |
||||
|
data => NotificationData.ToStandardData(wechatOptions.DefaultMsgPrefix, data)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// TODO: 后期改进,配置项集成到 <see cref="LINGYUN.Abp.WeChat.MiniProgram.AbpWeChatMiniProgramOptions"/>
|
||||
|
/// </summary>
|
||||
|
public class AbpNotificationsWeChatMiniProgramOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认消息头部标记
|
||||
|
/// </summary>
|
||||
|
public string DefaultMsgPrefix { get; set; } = "[wmp]"; |
||||
|
/// <summary>
|
||||
|
/// 默认小程序模板
|
||||
|
/// </summary>
|
||||
|
public string DefaultTemplateId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 默认跳转小程序类型
|
||||
|
/// </summary>
|
||||
|
public string DefaultState { get; set; } = "developer"; |
||||
|
/// <summary>
|
||||
|
/// 默认小程序语言
|
||||
|
/// </summary>
|
||||
|
public string DefaultLanguage { get; set; } = "zh_CN"; |
||||
|
} |
||||
|
} |
||||
20
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/Features/WeChatWeAppFeatureDefinitionProvider.cs → aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/Features/WeChatMiniProgramFeatureDefinitionProvider.cs
20
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/Features/WeChatWeAppFeatureDefinitionProvider.cs → aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/Features/WeChatMiniProgramFeatureDefinitionProvider.cs
@ -1,10 +1,10 @@ |
|||||
using LINGYUN.Abp.WeChat.Features; |
using LINGYUN.Abp.WeChat.Features; |
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp.Features |
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram.Features |
||||
{ |
{ |
||||
public static class WeChatWeAppFeatures |
public static class WeChatMiniProgramFeatures |
||||
{ |
{ |
||||
public const string GroupName = WeChatFeatures.GroupName + ".WeApp"; |
public const string GroupName = WeChatFeatures.GroupName + ".MiniProgram"; |
||||
|
|
||||
public static class Notifications |
public static class Notifications |
||||
{ |
{ |
||||
27
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs → aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs
27
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs → aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs
@ -0,0 +1,40 @@ |
|||||
|
# LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
|
||||
|
通知发布提供程序的微信小程序实现 |
||||
|
|
||||
|
大部分重写的模块都和官方模块名称保持一致,通过命名空间区分,主要是只改写了一小部分或者增加额外的功能 |
||||
|
如果大部分模块代码都重写,或者完全就是扩展模块,才会定义自己的名字 |
||||
|
|
||||
|
#### 注意 |
||||
|
|
||||
|
内置了通知数据 NotificationDataMappings 方法 |
||||
|
可通过 NotificationDataMappings.MappingAll(WeChatMiniProgramNotificationPublishProvider.ProviderName, Func<NotificationData, NotificationData> func) 来自定义规则 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
* 此配置项将在下一个微信相关大版本移除,合并到 LINGYUN.Abp.WeChat.MiniProgram.AbpWeChatMiniProgramOptions |
||||
|
|
||||
|
```json |
||||
|
|
||||
|
{ |
||||
|
"Notifications": { |
||||
|
"WeChat": { |
||||
|
"MiniProgram": { |
||||
|
"DefaultMsgPrefix": "默认消息头部标记", |
||||
|
"DefaultTemplateId": "默认小程序模板", |
||||
|
"DefaultState": "默认跳转小程序类型", |
||||
|
"DefaultLanguage": "默认小程序语言" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpNotificationsWeChatMiniProgramModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
@ -1,23 +0,0 @@ |
|||||
using LINGYUN.Abp.WeChat.MiniProgram; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Volo.Abp.Modularity; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpWeChatMiniProgramModule), |
|
||||
typeof(AbpNotificationModule))] |
|
||||
public class AbpNotificationsWeChatWeAppModule : AbpModule |
|
||||
{ |
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
Configure<AbpWeChatWeAppNotificationOptions>(configuration.GetSection("Notifications:WeChat:WeApp")); |
|
||||
|
|
||||
Configure<AbpNotificationOptions>(options => |
|
||||
{ |
|
||||
options.PublishProviders.Add<WeChatWeAppNotificationPublishProvider>(); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,22 +0,0 @@ |
|||||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|
||||
{ |
|
||||
public class AbpWeChatWeAppNotificationOptions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 默认消息头部标记
|
|
||||
/// </summary>
|
|
||||
public string DefaultMsgPrefix { get; set; } = "[wx]"; |
|
||||
/// <summary>
|
|
||||
/// 默认小程序模板
|
|
||||
/// </summary>
|
|
||||
public string DefaultTemplateId { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 默认跳转小程序类型
|
|
||||
/// </summary>
|
|
||||
public string DefaultWeAppState { get; set; } = "developer"; |
|
||||
/// <summary>
|
|
||||
/// 默认小程序语言
|
|
||||
/// </summary>
|
|
||||
public string DefaultWeAppLanguage { get; set; } = "zh_CN"; |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|
||||
{ |
|
||||
public interface IWeChatWeAppNotificationSender |
|
||||
{ |
|
||||
Task SendAsync(WeChatWeAppSendNotificationData notificationData, CancellationToken cancellationToken = default); |
|
||||
} |
|
||||
} |
|
||||
@ -1,123 +0,0 @@ |
|||||
using LINGYUN.Abp.Features.LimitValidation; |
|
||||
using LINGYUN.Abp.Notifications.WeChat.WeApp.Features; |
|
||||
using LINGYUN.Abp.WeChat.MiniProgram; |
|
||||
using LINGYUN.Abp.WeChat.Token; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Microsoft.Extensions.Logging.Abstractions; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Newtonsoft.Json; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Net.Http; |
|
||||
using System.Text; |
|
||||
using System.Threading; |
|
||||
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"; |
|
||||
public ILogger<WeChatWeAppNotificationSender> Logger { get; set; } |
|
||||
protected IHttpClientFactory HttpClientFactory { get; } |
|
||||
protected IJsonSerializer JsonSerializer { get; } |
|
||||
protected AbpWeChatMiniProgramOptions MiniProgramOptions { get; } |
|
||||
protected IWeChatTokenProvider WeChatTokenProvider { get; } |
|
||||
public WeChatWeAppNotificationSender( |
|
||||
IJsonSerializer jsonSerializer, |
|
||||
IHttpClientFactory httpClientFactory, |
|
||||
IWeChatTokenProvider weChatTokenProvider, |
|
||||
IOptions<AbpWeChatMiniProgramOptions> miniProgramOptions) |
|
||||
{ |
|
||||
JsonSerializer = jsonSerializer; |
|
||||
HttpClientFactory = httpClientFactory; |
|
||||
WeChatTokenProvider = weChatTokenProvider; |
|
||||
MiniProgramOptions = miniProgramOptions.Value; |
|
||||
|
|
||||
Logger = NullLogger<WeChatWeAppNotificationSender>.Instance; |
|
||||
} |
|
||||
|
|
||||
[RequiresLimitFeature( // 检查消息发布功能限制
|
|
||||
WeChatWeAppFeatures.Notifications.PublishLimit, |
|
||||
WeChatWeAppFeatures.Notifications.PublishLimitInterval, |
|
||||
LimitPolicy.Month, |
|
||||
WeChatWeAppFeatures.Notifications.DefaultPublishLimit, |
|
||||
WeChatWeAppFeatures.Notifications.DefaultPublishLimitInterval |
|
||||
)] |
|
||||
public virtual async Task SendAsync(WeChatWeAppSendNotificationData notificationData, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var weChatToken = await WeChatTokenProvider.GetTokenAsync(MiniProgramOptions.AppId, MiniProgramOptions.AppSecret, cancellationToken); |
|
||||
var requestParamters = new Dictionary<string, string> |
|
||||
{ |
|
||||
{ "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, cancellationToken); |
|
||||
var weChatSenNotificationResponse = JsonSerializer.Deserialize<WeChatSendNotificationResponse>(responseContent); |
|
||||
|
|
||||
if (!weChatSenNotificationResponse.IsSuccessed) |
|
||||
{ |
|
||||
Logger.LogWarning("Send wechat we app subscribe message failed"); |
|
||||
Logger.LogWarning($"Error code: {weChatSenNotificationResponse.ErrorCode}, message: {weChatSenNotificationResponse.ErrorMessage}"); |
|
||||
} |
|
||||
// 失败是否抛出异常
|
|
||||
// weChatSenNotificationResponse.ThrowIfNotSuccess();
|
|
||||
} |
|
||||
protected virtual async Task<string> MakeRequestAndGetResultAsync(string url, WeChatWeAppSendNotificationData notificationData, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var client = HttpClientFactory.CreateClient(SendNotificationClientName); |
|
||||
var sendDataContent = JsonSerializer.Serialize(notificationData); |
|
||||
var requestContent = new StringContent(sendDataContent); |
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) |
|
||||
{ |
|
||||
Content = requestContent |
|
||||
}; |
|
||||
|
|
||||
var response = await client.SendAsync(requestMessage, cancellationToken); |
|
||||
if (!response.IsSuccessStatusCode) |
|
||||
{ |
|
||||
throw new AbpException($"WeChat send subscribe message 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<string, string> 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 bool IsSuccessed => ErrorCode == 0; |
|
||||
|
|
||||
public void ThrowIfNotSuccess() |
|
||||
{ |
|
||||
if (ErrorCode != 0) |
|
||||
{ |
|
||||
throw new AbpException($"Send wechat weapp notification error:{ErrorMessage}"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,103 +0,0 @@ |
|||||
#pragma warning disable IDE1006 // 禁止编译器提示
|
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|
||||
{ |
|
||||
public class WeChatWeAppSendNotificationData |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 接收者(用户)的 openid
|
|
||||
/// </summary>
|
|
||||
public string touser { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 所需下发的订阅模板id
|
|
||||
/// </summary>
|
|
||||
public string template_id { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 点击模板卡片后的跳转页面,仅限本小程序内的页面。
|
|
||||
/// 支持带参数,(示例index?foo=bar)。
|
|
||||
/// 该字段不填则模板无跳转
|
|
||||
/// </summary>
|
|
||||
public string page { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 跳转小程序类型:
|
|
||||
/// developer为开发版;trial为体验版;formal为正式版;
|
|
||||
/// 默认为正式版
|
|
||||
/// </summary>
|
|
||||
public string miniprogram_state { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 进入小程序查看”的语言类型,
|
|
||||
/// 支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),
|
|
||||
/// 默认为zh_CN
|
|
||||
/// </summary>
|
|
||||
public string lang { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 模板内容,
|
|
||||
/// 格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
|
||||
/// </summary>
|
|
||||
public Dictionary<string, WeChatNotificationData> data { 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; |
|
||||
|
|
||||
data = new Dictionary<string, WeChatNotificationData>(); |
|
||||
} |
|
||||
/// <summary>
|
|
||||
/// 写入标准数据
|
|
||||
/// </summary>
|
|
||||
/// <param name="writeData"></param>
|
|
||||
/// <returns></returns>
|
|
||||
public WeChatWeAppSendNotificationData WriteStandardData(NotificationData writeData) |
|
||||
{ |
|
||||
foreach (var kv in writeData.Properties) |
|
||||
{ |
|
||||
if (!data.ContainsKey(kv.Key)) |
|
||||
{ |
|
||||
data.Add(kv.Key, new WeChatNotificationData(kv.Value)); |
|
||||
} |
|
||||
} |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
public WeChatWeAppSendNotificationData WriteData(string prefix, string key, object value) |
|
||||
{ |
|
||||
// 只截取符合标记的数据
|
|
||||
if (key.StartsWith(prefix)) |
|
||||
{ |
|
||||
key = key.Replace(prefix, ""); |
|
||||
if (!data.ContainsKey(key)) |
|
||||
{ |
|
||||
data.Add(key, new WeChatNotificationData(value)); |
|
||||
} |
|
||||
} |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
public WeChatWeAppSendNotificationData WriteData(string prefix, IDictionary<string, object> setData) |
|
||||
{ |
|
||||
foreach(var kv in setData) |
|
||||
{ |
|
||||
WriteData(prefix, kv.Key, kv.Value); |
|
||||
} |
|
||||
return this; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class WeChatNotificationData |
|
||||
{ |
|
||||
public object Value { get; } |
|
||||
|
|
||||
public WeChatNotificationData(object value) |
|
||||
{ |
|
||||
Value = value; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
#pragma warning restore IDE1006
|
|
||||
@ -1,24 +0,0 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
|
||||
|
|
||||
<Import Project="..\..\..\common.props" /> |
|
||||
|
|
||||
<PropertyGroup> |
|
||||
<TargetFramework>netstandard2.0</TargetFramework> |
|
||||
<RootNamespace /> |
|
||||
</PropertyGroup> |
|
||||
|
|
||||
<ItemGroup> |
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="3.1.8" /> |
|
||||
<PackageReference Include="Volo.Abp.Caching" Version="3.3.0" /> |
|
||||
<PackageReference Include="Volo.Abp.Json" Version="3.3.0" /> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
<ItemGroup> |
|
||||
<ProjectReference Include="..\..\wechat\LINGYUN.Abp.WeChat\LINGYUN.Abp.WeChat.csproj" /> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
<ItemGroup> |
|
||||
<Folder Include="LINGYUN\Abp\WeChat\Authorization\OpenId\" /> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
</Project> |
|
||||
@ -1,72 +0,0 @@ |
|||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 与微信认证相关的静态(可变)常量
|
|
||||
/// </summary>
|
|
||||
public static class AbpWeChatAuthorizationConsts |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 微信授权名称
|
|
||||
/// </summary>
|
|
||||
public const string AuthenticationScheme = "WeChat"; |
|
||||
/// <summary>
|
|
||||
/// 微信授权显示名称
|
|
||||
/// </summary>
|
|
||||
public static string DisplayName = "WeChat"; |
|
||||
/// <summary>
|
|
||||
/// 微信个人信息标识
|
|
||||
/// </summary>
|
|
||||
public static string ProfileKey { get; set; } = "wechat.profile"; |
|
||||
/// <summary>
|
|
||||
/// 微信提供者标识
|
|
||||
/// </summary>
|
|
||||
public static string ProviderKey { get; set; } = AuthenticationScheme; |
|
||||
/// <summary>
|
|
||||
/// 回调地址
|
|
||||
/// </summary>
|
|
||||
public static string CallbackPath { get; set; } = "/signin-wechat"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 微信客户端外的网页登录
|
|
||||
/// </summary>
|
|
||||
public const string QrConnectEndpoint = "https://open.weixin.qq.com/connect/qrconnect"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 微信客户端内的网页登录
|
|
||||
/// </summary>
|
|
||||
public const string AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 用户允许授权后通过返回的code换取access_token地址
|
|
||||
/// </summary>
|
|
||||
public const string TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token"; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 使用access_token获取用户个人信息地址
|
|
||||
/// </summary>
|
|
||||
public const string UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo"; |
|
||||
/// <summary>
|
|
||||
/// 弹出授权页面,可通过openid拿到昵称、性别、所在地。
|
|
||||
/// 并且, 即使在未关注的情况下,只要用户授权,也能获取其信息
|
|
||||
/// <br />
|
|
||||
/// <br />
|
|
||||
/// 详询: <see cref="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html"/>
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。
|
|
||||
/// 但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息
|
|
||||
/// </remarks>
|
|
||||
public const string UserInfoScope = "snsapi_userinfo"; |
|
||||
/// <summary>
|
|
||||
/// 不弹出授权页面,直接跳转,只能获取用户openid
|
|
||||
/// <br />
|
|
||||
/// <br />
|
|
||||
/// 详询: <see cref="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html"/>
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。
|
|
||||
/// 用户感知的就是直接进入了回调页(往往是业务页面)
|
|
||||
/// </remarks>
|
|
||||
public const string LoginScope = "snsapi_login"; |
|
||||
} |
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
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(AbpWeChatModule), |
|
||||
typeof(AbpJsonModule), |
|
||||
typeof(AbpCachingModule))] |
|
||||
public class AbpWeChatAuthorizationModule : AbpModule |
|
||||
{ |
|
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
|
||||
{ |
|
||||
var configuration = context.Services.GetConfiguration(); |
|
||||
Configure<AbpWeChatAuthorizationOptions>(configuration.GetSection("WeChat:Auth")); |
|
||||
|
|
||||
context.Services.AddHttpClient("WeChatRequestClient", options => |
|
||||
{ |
|
||||
options.BaseAddress = new Uri("https://api.weixin.qq.com"); |
|
||||
}).AddTransientHttpErrorPolicy(builder => |
|
||||
builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
public class AbpWeChatAuthorizationOptions |
|
||||
{ |
|
||||
public string AppId { get; set; } |
|
||||
|
|
||||
public string AppSecret { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
using LINGYUN.Abp.WeChat.Authorization.Settings; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using Volo.Abp.Options; |
|
||||
using Volo.Abp.Settings; |
|
||||
using Volo.Abp.Threading; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
public class AbpWeChatAuthorizationOptionsFactory : AbpOptionsFactory<AbpWeChatAuthorizationOptions> |
|
||||
{ |
|
||||
protected ISettingProvider SettingProvider { get; } |
|
||||
|
|
||||
public AbpWeChatAuthorizationOptionsFactory( |
|
||||
ISettingProvider settingProvider, |
|
||||
IEnumerable<IConfigureOptions<AbpWeChatAuthorizationOptions>> setups, |
|
||||
IEnumerable<IPostConfigureOptions<AbpWeChatAuthorizationOptions>> postConfigures) |
|
||||
: base(setups, postConfigures) |
|
||||
{ |
|
||||
SettingProvider = settingProvider; |
|
||||
} |
|
||||
|
|
||||
public override AbpWeChatAuthorizationOptions Create(string name) |
|
||||
{ |
|
||||
var options = base.Create(name); |
|
||||
|
|
||||
OverrideOptions(options); |
|
||||
|
|
||||
return options; |
|
||||
} |
|
||||
|
|
||||
protected virtual void OverrideOptions(AbpWeChatAuthorizationOptions options) |
|
||||
{ |
|
||||
AsyncHelper.RunSync(() => OverrideOptionsAsync(options)); |
|
||||
} |
|
||||
|
|
||||
protected virtual async Task OverrideOptionsAsync(AbpWeChatAuthorizationOptions options) |
|
||||
{ |
|
||||
var appId = await SettingProvider.GetOrNullAsync(WeChatAuthorizationSettingNames.AppId); |
|
||||
var appSecret = await SettingProvider.GetOrNullAsync(WeChatAuthorizationSettingNames.AppSecret); |
|
||||
|
|
||||
options.AppId = appId ?? options.AppId; |
|
||||
options.AppSecret = appSecret ?? options.AppSecret; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,48 +0,0 @@ |
|||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 微信认证身份类型,可以像 <see cref="Volo.Abp.Security.Claims.AbpClaimTypes"/> 自行配置
|
|
||||
/// <br />
|
|
||||
/// See: <see cref="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html"/>
|
|
||||
/// </summary>
|
|
||||
public class AbpWeChatClaimTypes |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 用户的唯一标识
|
|
||||
/// </summary>
|
|
||||
public static string OpenId { get; set; } = "wx-openid"; // 可变更
|
|
||||
/// <summary>
|
|
||||
/// 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
|
|
||||
/// </summary>
|
|
||||
public static string UnionId { get; set; } = "wx-unionid"; //可变更
|
|
||||
/// <summary>
|
|
||||
/// 用户昵称
|
|
||||
/// </summary>
|
|
||||
public static string NickName { get; set; } = "nickname"; |
|
||||
/// <summary>
|
|
||||
/// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
|
|
||||
/// </summary>
|
|
||||
public static string Sex { get; set; } = "sex"; |
|
||||
/// <summary>
|
|
||||
/// 国家,如中国为CN
|
|
||||
/// </summary>
|
|
||||
public static string Country { get; set; } = "country"; |
|
||||
/// <summary>
|
|
||||
/// 用户个人资料填写的省份
|
|
||||
/// </summary>
|
|
||||
public static string Province { get; set; } = "province"; |
|
||||
/// <summary>
|
|
||||
/// 普通用户个人资料填写的城市
|
|
||||
/// </summary>
|
|
||||
public static string City { get; set; } = "city"; |
|
||||
/// <summary>
|
|
||||
/// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。
|
|
||||
/// 若用户更换头像,原有头像URL将失效。
|
|
||||
/// </summary>
|
|
||||
public static string AvatarUrl { get; set; } = "avatar"; |
|
||||
/// <summary>
|
|
||||
/// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
|
|
||||
/// </summary>
|
|
||||
public static string Privilege { get; set; } = "privilege"; |
|
||||
} |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
using LINGYUN.Abp.WeChat.Settings; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Authorization.Settings |
|
||||
{ |
|
||||
public class WeChatAuthorizationSettingNames |
|
||||
{ |
|
||||
private const string Prefix = WeChatSettingNames.Prefix + ".Authorization"; |
|
||||
public static string AppId = Prefix + "." + nameof(AbpWeChatAuthorizationOptions.AppId); |
|
||||
public static string AppSecret = Prefix + "." + nameof(AbpWeChatAuthorizationOptions.AppSecret); |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
using LINGYUN.Abp.WeChat.Localization; |
|
||||
using Volo.Abp.Localization; |
|
||||
using Volo.Abp.Settings; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Authorization.Settings |
|
||||
{ |
|
||||
public class WeChatAuthorizationSettingProvider : SettingDefinitionProvider |
|
||||
{ |
|
||||
public override void Define(ISettingDefinitionContext context) |
|
||||
{ |
|
||||
context.Add( |
|
||||
new SettingDefinition( |
|
||||
WeChatAuthorizationSettingNames.AppId, "", |
|
||||
L("DisplayName:WeChat.Auth.AppId"), |
|
||||
L("Description:WeChat.Auth.AppId"), |
|
||||
isVisibleToClients: true, |
|
||||
isEncrypted: true), |
|
||||
new SettingDefinition( |
|
||||
WeChatAuthorizationSettingNames.AppSecret, "", |
|
||||
L("DisplayName:WeChat.Auth.AppSecret"), |
|
||||
L("Description:WeChat.Auth.AppSecret"), |
|
||||
isVisibleToClients: true, |
|
||||
isEncrypted: true) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
protected ILocalizableString L(string name) |
|
||||
{ |
|
||||
return LocalizableString.Create<WeChatResource>(name); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
public interface IWeChatTokenProvider |
|
||||
{ |
|
||||
Task<WeChatToken> GetTokenAsync(CancellationToken cancellationToken = default); |
|
||||
} |
|
||||
} |
|
||||
@ -1,26 +0,0 @@ |
|||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 微信令牌
|
|
||||
/// </summary>
|
|
||||
public class WeChatToken |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 访问令牌
|
|
||||
/// </summary>
|
|
||||
public string AccessToken { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 过期时间,单位(s)
|
|
||||
/// </summary>
|
|
||||
public int ExpiresIn { get; set; } |
|
||||
public WeChatToken() |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
public WeChatToken(string token, int expiresIn) |
|
||||
{ |
|
||||
AccessToken = token; |
|
||||
ExpiresIn = expiresIn; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,89 +0,0 @@ |
|||||
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; |
|
||||
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<WeChatTokenProvider> Logger { get; set; } |
|
||||
protected IHttpClientFactory HttpClientFactory { get; } |
|
||||
protected IJsonSerializer JsonSerializer { get; } |
|
||||
protected IDistributedCache<WeChatTokenCacheItem> Cache { get; } |
|
||||
protected AbpWeChatAuthorizationOptions Options { get; } |
|
||||
public WeChatTokenProvider( |
|
||||
IJsonSerializer jsonSerializer, |
|
||||
IHttpClientFactory httpClientFactory, |
|
||||
IOptions<AbpWeChatAuthorizationOptions> options, |
|
||||
IDistributedCache<WeChatTokenCacheItem> cache) |
|
||||
{ |
|
||||
JsonSerializer = jsonSerializer; |
|
||||
HttpClientFactory = httpClientFactory; |
|
||||
|
|
||||
Cache = cache; |
|
||||
Options = options.Value; |
|
||||
|
|
||||
Logger = NullLogger<WeChatTokenProvider>.Instance; |
|
||||
} |
|
||||
|
|
||||
public virtual async Task<WeChatToken> GetTokenAsync(CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return (await GetCacheItemAsync("WeChatToken", Options.AppId, cancellationToken)).WeChatToken; |
|
||||
} |
|
||||
|
|
||||
protected virtual async Task<WeChatTokenCacheItem> GetCacheItemAsync(string provider, string appId, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var cacheKey = WeChatTokenCacheItem.CalculateCacheKey(provider, appId); |
|
||||
|
|
||||
Logger.LogDebug($"WeChatTokenProvider.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.CreateClient("WeChatRequestClient"); |
|
||||
|
|
||||
var request = new WeChatTokenRequest |
|
||||
{ |
|
||||
BaseUrl = client.BaseAddress.AbsoluteUri, |
|
||||
AppSecret = Options.AppSecret, |
|
||||
AppId = Options.AppId, |
|
||||
GrantType = "client_credential" |
|
||||
}; |
|
||||
|
|
||||
var response = await client.RequestWeChatCodeTokenAsync(request, cancellationToken); |
|
||||
var responseContent = await response.Content.ReadAsStringAsync(); |
|
||||
var weChatTokenResponse = JsonSerializer.Deserialize<WeChatTokenResponse>(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, token: cancellationToken); |
|
||||
|
|
||||
Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); |
|
||||
|
|
||||
return cacheItem; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
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; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
using Newtonsoft.Json; |
|
||||
using Volo.Abp; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Authorization |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 微信访问令牌返回对象
|
|
||||
/// </summary>
|
|
||||
public class WeChatTokenResponse |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 错误码
|
|
||||
/// </summary>
|
|
||||
[JsonProperty("errcode")] |
|
||||
public int ErrorCode { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 错误消息
|
|
||||
/// </summary>
|
|
||||
[JsonProperty("errmsg")] |
|
||||
public string ErrorMessage { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 访问令牌
|
|
||||
/// </summary>
|
|
||||
[JsonProperty("access_token")] |
|
||||
public string AccessToken { get; set; } |
|
||||
/// <summary>
|
|
||||
/// 过期时间,单位(s)
|
|
||||
/// </summary>
|
|
||||
[JsonProperty("expires_in")] |
|
||||
public int ExpiresIn { get; set; } |
|
||||
|
|
||||
public WeChatToken ToWeChatToken() |
|
||||
{ |
|
||||
if(ErrorCode != 0) |
|
||||
{ |
|
||||
throw new AbpException(ErrorMessage); |
|
||||
} |
|
||||
return new WeChatToken(AccessToken, ExpiresIn); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
# LINGYUN.Abp.WeChat.Authorization |
|
||||
|
|
||||
废弃模块,模块层次不清晰,微信有多端平台,不同平台授权规则不一致 |
|
||||
|
|
||||
#### 注意 |
|
||||
|
|
||||
|
|
||||
|
|
||||
## 配置使用 |
|
||||
|
|
||||
|
|
||||
```csharp |
|
||||
[DependsOn(typeof(AbpWeChatAuthorizationModule))] |
|
||||
public class YouProjectModule : AbpModule |
|
||||
{ |
|
||||
// other |
|
||||
} |
|
||||
@ -1,45 +0,0 @@ |
|||||
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<HttpResponseMessage> RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatTokenRequest request, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var getResuestUrlBuilder = new StringBuilder(); |
|
||||
getResuestUrlBuilder.Append(request.BaseUrl); |
|
||||
getResuestUrlBuilder.Append("cgi-bin/token"); |
|
||||
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; |
|
||||
} |
|
||||
|
|
||||
public static async Task<HttpResponseMessage> RequestWeChatOpenIdAsync(this HttpMessageInvoker client, WeChatOpenIdRequest request, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
var getResuestUrlBuiilder = new StringBuilder(); |
|
||||
getResuestUrlBuiilder.Append(request.BaseUrl); |
|
||||
getResuestUrlBuiilder.Append("sns/jscode2session"); |
|
||||
getResuestUrlBuiilder.AppendFormat("?appid={0}", request.AppId); |
|
||||
getResuestUrlBuiilder.AppendFormat("&secret={0}", request.Secret); |
|
||||
getResuestUrlBuiilder.AppendFormat("&js_code={0}", request.Code); |
|
||||
getResuestUrlBuiilder.Append("&grant_type=authorization_code"); |
|
||||
|
|
||||
var getRequest = new HttpRequestMessage(HttpMethod.Get, getResuestUrlBuiilder.ToString()); |
|
||||
HttpResponseMessage httpResponse; |
|
||||
|
|
||||
httpResponse = await client.SendAsync(getRequest, cancellationToken).ConfigureAwait(false); |
|
||||
|
|
||||
return httpResponse; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,39 +0,0 @@ |
|||||
using LINGYUN.Abp.WeChat.Authorization; |
|
||||
|
|
||||
namespace Volo.Abp.Users |
|
||||
{ |
|
||||
public static class CurrentUserExtensions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// 获取用户微信id,如果不存在返回空值
|
|
||||
/// </summary>
|
|
||||
/// <param name="currentUser"></param>
|
|
||||
/// <returns></returns>
|
|
||||
public static string FindWeChatOpenId(this ICurrentUser currentUser) |
|
||||
{ |
|
||||
var weChatClaim = currentUser.FindClaim(AbpWeChatClaimTypes.OpenId); |
|
||||
if (weChatClaim == null) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
return weChatClaim.Value; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// 获取微信用户主体id,如果不存在返回空值
|
|
||||
/// </summary>
|
|
||||
/// <param name="currentUser"></param>
|
|
||||
/// <returns></returns>
|
|
||||
public static string FindWeChatUnionId(this ICurrentUser currentUser) |
|
||||
{ |
|
||||
var weChatClaim = currentUser.FindClaim(AbpWeChatClaimTypes.UnionId); |
|
||||
if (weChatClaim == null) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
return weChatClaim.Value; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.1" /> |
||||
|
<PackageReference Include="Shouldly" Version="3.0.2" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.Notifications.Sms\LINGYUN.Abp.Notifications.Sms.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Tests\LINGYUN.Abp.Notifications.Tests.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
public class AbpNotificationsSmsTestsBase : AbpTestsBase<AbpNotificationsSmsTestsModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationsSmsModule), |
||||
|
typeof(AbpNotificationsTestsModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpNotificationsSmsTestsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// 改变默认数据前缀方法
|
||||
|
PreConfigure<AbpNotificationsSmsOptions>(options => |
||||
|
{ |
||||
|
options.TemplateParamsPrefix = "[sms-override]"; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// 自定义数据处理方法
|
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
// 这条通知返回标准化的通知
|
||||
|
options.NotificationDataMappings |
||||
|
.Mapping( |
||||
|
SmsNotificationPublishProvider.ProviderName, |
||||
|
NotificationsTestsNames.Test2, |
||||
|
data => NotificationData.ToStandardData(data)); |
||||
|
|
||||
|
// 这条通知不做任何处理
|
||||
|
options.NotificationDataMappings |
||||
|
.Mapping( |
||||
|
SmsNotificationPublishProvider.ProviderName, |
||||
|
NotificationsTestsNames.Test3, |
||||
|
data => data); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Sms |
||||
|
{ |
||||
|
public class SmsNotificationDataMapping_Tests : AbpNotificationsSmsTestsBase |
||||
|
{ |
||||
|
private readonly NotificationData _notificationData; |
||||
|
protected AbpNotificationOptions NotificationOptions { get; } |
||||
|
protected AbpNotificationsSmsOptions NotificationSmsOptions { get; } |
||||
|
public SmsNotificationDataMapping_Tests() |
||||
|
{ |
||||
|
NotificationOptions = GetRequiredService<IOptions<AbpNotificationOptions>>().Value; |
||||
|
NotificationSmsOptions = GetRequiredService<IOptions<AbpNotificationsSmsOptions>>().Value; |
||||
|
|
||||
|
_notificationData = new NotificationData(); |
||||
|
InitNotificationData(_notificationData); |
||||
|
} |
||||
|
|
||||
|
private void InitNotificationData(NotificationData data) |
||||
|
{ |
||||
|
data.WriteStandardData("title", "message", DateTime.Now, "formUser", "description"); |
||||
|
data.WriteStandardData(NotificationSmsOptions.TemplateParamsPrefix, "phoneNumber", "13800138000"); |
||||
|
data.WriteStandardData(NotificationSmsOptions.TemplateParamsPrefix, "template", "SM_202011250901"); |
||||
|
data.TrySetData("otherDataKey", "otherDataValue"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_Sms_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingSmsItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(SmsNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test1); |
||||
|
|
||||
|
mappingSmsItem.ShouldNotBeNull(); |
||||
|
|
||||
|
var mappingSmsData = mappingSmsItem.MappingFunc(_notificationData); |
||||
|
mappingSmsData.TryGetData("phoneNumber").ShouldNotBeNull(); |
||||
|
mappingSmsData.TryGetData("phoneNumber").ToString().ShouldBe("13800138000"); |
||||
|
|
||||
|
mappingSmsData.TryGetData("template").ShouldNotBeNull(); |
||||
|
mappingSmsData.TryGetData("template").ToString().ShouldBe("SM_202011250901"); |
||||
|
|
||||
|
// 按照预定义规则,这条数据被丢弃
|
||||
|
mappingSmsData.TryGetData("otherDataKey").ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_Standard_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingStandardItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(SmsNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test2); |
||||
|
|
||||
|
var mappingStandardData = mappingStandardItem.MappingFunc(_notificationData); |
||||
|
|
||||
|
// 按照自定义规则,其他数据被丢弃
|
||||
|
mappingStandardData.TryGetData("phoneNumber").ShouldBeNull(); |
||||
|
mappingStandardData.TryGetData("template").ShouldBeNull(); |
||||
|
mappingStandardData.TryGetData("otherDataKey").ShouldBeNull(); |
||||
|
mappingStandardData.Properties.Count.ShouldBe(6); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_Origin_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingOriginItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(SmsNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test3); |
||||
|
|
||||
|
var mappingOriginData = mappingOriginItem.MappingFunc(_notificationData); |
||||
|
|
||||
|
// 按照自定义规则,所有数据被保留
|
||||
|
mappingOriginData.TryGetData(NotificationSmsOptions.TemplateParamsPrefix + "phoneNumber").ShouldNotBeNull(); |
||||
|
mappingOriginData.TryGetData(NotificationSmsOptions.TemplateParamsPrefix + "template").ShouldNotBeNull(); |
||||
|
mappingOriginData.TryGetData("otherDataKey").ShouldNotBeNull(); |
||||
|
mappingOriginData.Properties.Count.ShouldBe(9); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.1" /> |
||||
|
<PackageReference Include="Shouldly" Version="3.0.2" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class AbpNotificationsTestsBase : AbpTestsBase<AbpNotificationsTestsModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpNotificationsTestsModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationsTestsDefinitionProvider : NotificationDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(INotificationDefinitionContext context) |
||||
|
{ |
||||
|
var group = context.AddGroup(NotificationsTestsNames.GroupName); |
||||
|
|
||||
|
group.AddNotification(NotificationsTestsNames.Test1, |
||||
|
notificationType: NotificationType.Application, |
||||
|
lifetime: NotificationLifetime.OnlyOne); |
||||
|
|
||||
|
group.AddNotification(NotificationsTestsNames.Test2, |
||||
|
notificationType: NotificationType.Application, |
||||
|
lifetime: NotificationLifetime.Persistent); |
||||
|
|
||||
|
group.AddNotification(NotificationsTestsNames.Test3, |
||||
|
notificationType: NotificationType.User, |
||||
|
lifetime: NotificationLifetime.OnlyOne); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Shouldly; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public class NotificationsTestsDefinitionProvider_Tests : AbpNotificationsTestsBase |
||||
|
{ |
||||
|
protected INotificationDefinitionManager NotificationDefinitionManager { get; } |
||||
|
|
||||
|
public NotificationsTestsDefinitionProvider_Tests() |
||||
|
{ |
||||
|
NotificationDefinitionManager = GetRequiredService<INotificationDefinitionManager>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void GetGroups_Test() |
||||
|
{ |
||||
|
var groups = NotificationDefinitionManager.GetGroups(); |
||||
|
groups.Count.ShouldBe(1); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void GetAll_Test() |
||||
|
{ |
||||
|
var notifications = NotificationDefinitionManager.GetAll(); |
||||
|
notifications.Count.ShouldBe(3); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void GetOrNull_Test() |
||||
|
{ |
||||
|
NotificationDefinitionManager.GetOrNull(NotificationsTestsNames.Test2).ShouldNotBeNull(); |
||||
|
NotificationDefinitionManager.GetOrNull(NotificationsTestsNames.Test3).ShouldNotBeNull(); |
||||
|
NotificationDefinitionManager.GetOrNull("NullOfNotification").ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(NotificationsTestsNames.Test1)] |
||||
|
public void Get_Test(string name) |
||||
|
{ |
||||
|
var notification = NotificationDefinitionManager.Get(name); |
||||
|
notification.Name.ShouldBe(name); |
||||
|
notification.DisplayName.ShouldBeOfType<FixedLocalizableString>(); |
||||
|
notification.Description.ShouldBeNull(); |
||||
|
notification.AllowSubscriptionToClients.ShouldBeFalse(); |
||||
|
notification.NotificationLifetime.ShouldBe(NotificationLifetime.OnlyOne); |
||||
|
notification.NotificationType.ShouldBe(NotificationType.Application); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public static class NotificationsTestsNames |
||||
|
{ |
||||
|
public const string GroupName = "Abp.Notifications"; |
||||
|
|
||||
|
public const string Test1 = GroupName + ".Test1"; |
||||
|
|
||||
|
public const string Test2 = GroupName + ".Test2"; |
||||
|
|
||||
|
public const string Test3 = GroupName + ".Test3"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.1" /> |
||||
|
<PackageReference Include="Shouldly" Version="3.0.2" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\wechat\LINGYUN.Abp.Notifications.WeChat.MiniProgram\LINGYUN.Abp.Notifications.WeChat.MiniProgram.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Tests\LINGYUN.Abp.Notifications.Tests.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
{ |
||||
|
public class AbpNotificationsWeChatMiniProgramTestsBase : AbpTestsBase<AbpNotificationsWeChatMiniProgramTestsModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationsWeChatMiniProgramModule), |
||||
|
typeof(AbpNotificationsTestsModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpNotificationsWeChatMiniProgramTestsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<AbpNotificationsWeChatMiniProgramOptions>(options => |
||||
|
{ |
||||
|
options.DefaultMsgPrefix = "[wmp-override]"; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// 自定义数据处理方法
|
||||
|
Configure<AbpNotificationOptions>(options => |
||||
|
{ |
||||
|
// 这条通知返回标准化的通知
|
||||
|
options.NotificationDataMappings |
||||
|
.Mapping( |
||||
|
WeChatMiniProgramNotificationPublishProvider.ProviderName, |
||||
|
NotificationsTestsNames.Test2, |
||||
|
data => NotificationData.ToStandardData(data)); |
||||
|
|
||||
|
// 这条通知不做任何处理
|
||||
|
options.NotificationDataMappings |
||||
|
.Mapping( |
||||
|
WeChatMiniProgramNotificationPublishProvider.ProviderName, |
||||
|
NotificationsTestsNames.Test3, |
||||
|
data => data); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.WeChat.MiniProgram |
||||
|
{ |
||||
|
public class WeChatMiniProgramNotificationDataMapping_Tests : AbpNotificationsWeChatMiniProgramTestsBase |
||||
|
{ |
||||
|
private readonly NotificationData _notificationData; |
||||
|
protected AbpNotificationOptions NotificationOptions { get; } |
||||
|
protected AbpNotificationsWeChatMiniProgramOptions NotificationWeChatMiniProgramOptions { get; } |
||||
|
public WeChatMiniProgramNotificationDataMapping_Tests() |
||||
|
{ |
||||
|
NotificationOptions = GetRequiredService<IOptions<AbpNotificationOptions>>().Value; |
||||
|
NotificationWeChatMiniProgramOptions = GetRequiredService<IOptions<AbpNotificationsWeChatMiniProgramOptions>>().Value; |
||||
|
|
||||
|
_notificationData = new NotificationData(); |
||||
|
InitNotificationData(_notificationData); |
||||
|
} |
||||
|
|
||||
|
private void InitNotificationData(NotificationData data) |
||||
|
{ |
||||
|
data.WriteStandardData("title", "message", DateTime.Now, "formUser", "description"); |
||||
|
data.WriteStandardData(NotificationWeChatMiniProgramOptions.DefaultMsgPrefix, "openid", "TEST"); |
||||
|
data.TrySetData("otherDataKey", "otherDataValue"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_WeChatMiniProgram_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingOpenIdItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(WeChatMiniProgramNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test1); |
||||
|
|
||||
|
mappingOpenIdItem.ShouldNotBeNull(); |
||||
|
|
||||
|
var mappingOpenIdData = mappingOpenIdItem.MappingFunc(_notificationData); |
||||
|
mappingOpenIdData.TryGetData("openid").ShouldNotBeNull(); |
||||
|
mappingOpenIdData.TryGetData("openid").ToString().ShouldBe("TEST"); |
||||
|
|
||||
|
// 按照预定义规则,这条数据被丢弃
|
||||
|
mappingOpenIdData.TryGetData("otherDataKey").ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_Standard_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingStandardItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(WeChatMiniProgramNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test2); |
||||
|
|
||||
|
var mappingStandardData = mappingStandardItem.MappingFunc(_notificationData); |
||||
|
|
||||
|
// 按照自定义规则,其他数据被丢弃
|
||||
|
mappingStandardData.TryGetData("openid").ShouldBeNull(); |
||||
|
mappingStandardData.TryGetData("otherDataKey").ShouldBeNull(); |
||||
|
mappingStandardData.Properties.Count.ShouldBe(6); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Mapping_Origin_Notification_Data_Test() |
||||
|
{ |
||||
|
var mappingOriginItem = NotificationOptions |
||||
|
.NotificationDataMappings |
||||
|
.GetMapItemOrDefault(WeChatMiniProgramNotificationPublishProvider.ProviderName, NotificationsTestsNames.Test3); |
||||
|
|
||||
|
var mappingOriginData = mappingOriginItem.MappingFunc(_notificationData); |
||||
|
|
||||
|
// 按照自定义规则,所有数据被保留
|
||||
|
mappingOriginData.TryGetData(NotificationWeChatMiniProgramOptions.DefaultMsgPrefix + "openid").ShouldNotBeNull(); |
||||
|
mappingOriginData.TryGetData("otherDataKey").ShouldNotBeNull(); |
||||
|
mappingOriginData.Properties.Count.ShouldBe(8); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue