diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs index 1d9bcc629..32c12c193 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs @@ -152,6 +152,20 @@ public class AliyunSettingAppService : ApplicationService, IAliyunSettingAppServ await SettingManager.GetOrNullAsync(AliyunSettingNames.Sms.VisableErrorToClient, providerName, providerKey), ValueType.Boolean, providerName); + + var smsVerifyCodeSetting = aliyunSettingGroup.AddSetting(L["DisplayName:Aliyun.SmsVerifyCode"], L["Description:Aliyun.SmsVerifyCode"]); + smsVerifyCodeSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName, providerName, providerKey), + ValueType.String, + providerName); + smsVerifyCodeSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode, providerName, providerKey), + ValueType.String, + providerName); } #endregion diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json index 314098cdf..0c2e819f4 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json @@ -34,6 +34,8 @@ "Description:Policy": "Policy", "DisplayName:Aliyun.Sms": "Sms", "Description:Aliyun.Sms": "Sms", + "DisplayName:Aliyun.SmsVerifyCode": "Sms Verify Code", + "Description:Aliyun.SmsVerifyCode": "Sms Verify Code", "DisplayName:ActionName": "Action Name", "Description:ActionName": "Action Name", "DisplayName:DefaultSignName": "Default Sign Name", diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json index 1e9a8b872..e88e82bf4 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json @@ -34,6 +34,8 @@ "Description:Policy": "生成STS Token时可以指定一个额外的权限策略,以进一步限制STS Token的权限", "DisplayName:Aliyun.Sms": "短信服务", "Description:Aliyun.Sms": "阿里云短信服务", + "DisplayName:Aliyun.SmsVerifyCode": "短信认证服务", + "Description:Aliyun.SmsVerifyCode": "阿里云短信认证服务", "DisplayName:ActionName": "发送短信方法", "Description:ActionName": "发送短信方法名称,详情见阿里云Sms服务", "DisplayName:DefaultSignName": "默认短信签名", diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs index 0220a6ae2..54ea316eb 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs @@ -79,4 +79,20 @@ public static class AliyunSettingNames /// public const string VisableErrorToClient = Prefix + ".VisableErrorToClient"; } + + /// + /// 云通信号码认证服务 + /// + public class SmsVerifyCode + { + public const string Prefix = AliyunSettingNames.Prefix + ".SmsVerifyCode"; + /// + /// 默认签名 + /// + public const string DefaultSignName = Prefix + ".DefaultSignName"; + /// + /// 默认短信模板号 + /// + public const string DefaultTemplateCode = Prefix + ".DefaultTemplateCode"; + } } diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs index 55713d7af..3a8133c95 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs @@ -10,6 +10,7 @@ public class AliyunSettingProvider : SettingDefinitionProvider { context.Add(GetAuthorizationSettings()); context.Add(GetSmsSettings()); + context.Add(GetSmsVerifyCodeSettings()); } private SettingDefinition[] GetAuthorizationSettings() @@ -204,6 +205,37 @@ public class AliyunSettingProvider : SettingDefinitionProvider TenantSettingValueProvider.ProviderName) }; } + + private SettingDefinition[] GetSmsVerifyCodeSettings() + { + return new SettingDefinition[] + { + new SettingDefinition( + AliyunSettingNames.SmsVerifyCode.DefaultSignName, + displayName: L("DisplayName:DefaultSignName"), + description: L("Description:DefaultSignName"), + isVisibleToClients: false, + isEncrypted: true + ) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + new SettingDefinition( + AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode, + displayName: L("DisplayName:DefaultTemplateCode"), + description: L("Description:DefaultTemplateCode"), + isVisibleToClients: false, + isEncrypted: true + ) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + }; + } private ILocalizableString L(string name) { return LocalizableString.Create(name); diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs index 31326203b..5ce110221 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs @@ -20,9 +20,12 @@ using Volo.Abp.Sms; namespace LINGYUN.Abp.Sms.Aliyun; [Dependency(ServiceLifetime.Singleton)] -[ExposeServices(typeof(ISmsSender), typeof(AliyunSmsSender))] +[ExposeServices( + typeof(ISmsSender), + typeof(IAliyunSmsVerifyCodeSender), + typeof(AliyunSmsSender))] [RequiresFeature(AliyunFeatureNames.Sms.Enable)] -public class AliyunSmsSender : ISmsSender +public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender { protected IJsonSerializer JsonSerializer { get; } protected ISettingProvider SettingProvider { get; } @@ -94,6 +97,63 @@ public class AliyunSmsSender : ISmsSender } } + [RequiresLimitFeature( + AliyunFeatureNames.Sms.SendLimit, + AliyunFeatureNames.Sms.SendLimitInterval, + LimitPolicy.Month, + AliyunFeatureNames.Sms.DefaultSendLimit, + AliyunFeatureNames.Sms.DefaultSendLimitInterval)] + public async virtual Task SendAsync(SmsVerifyCodeMessage message) + { + var domain = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.Domain); + var version = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.Version); + var signName = message.SignName ?? + await SettingProvider.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName); + var templateCode = message.TemplateCode ?? + await SettingProvider.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode); + + Check.NotNullOrWhiteSpace(domain, AliyunSettingNames.Sms.Domain); + Check.NotNullOrWhiteSpace(version, AliyunSettingNames.Sms.Version); + Check.NotNullOrWhiteSpace(signName, AliyunSettingNames.SmsVerifyCode.DefaultSignName); + Check.NotNullOrWhiteSpace(templateCode, AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode); + + var request = new CommonRequest + { + Domain = domain, + Version = version, + Method = MethodType.POST, + Action = "SendSmsVerifyCode", + }; + request.AddBodyParameters("PhoneNumber", message.PhoneNumber); + request.AddBodyParameters("SignName", signName); + request.AddBodyParameters("TemplateCode", templateCode); + request.AddBodyParameters("TemplateParam", JsonSerializer.Serialize(message.TemplateParam)); + + try + { + var client = await AcsClientFactory.CreateAsync(); + var response = client.GetCommonResponse(request); + var responseContent = Encoding.Default.GetString(response.HttpResponse.Content); + var aliyunResponse = JsonSerializer.Deserialize(responseContent); + if (!aliyunResponse.Success) + { + if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient)) + { + throw new UserFriendlyException(aliyunResponse.Code, aliyunResponse.Message); + } + throw new AliyunSmsException(aliyunResponse.Code, $"Text message sending failed, code:{aliyunResponse.Code}, message:{aliyunResponse.Message}!"); + } + } + catch (ServerException se) + { + throw new AliyunSmsException(se.ErrorCode, $"Sending text messages to aliyun server is abnormal,type: {se.ErrorType}, error: {se.ErrorMessage}"); + } + catch (ClientException ce) + { + throw new AliyunSmsException(ce.ErrorCode, $"A client exception occurred in sending SMS messages,type: {ce.ErrorType}, error: {ce.ErrorMessage}"); + } + } + private async Task TryAddTemplateCodeAsync(CommonRequest request, SmsMessage smsMessage) { if (smsMessage.Properties.TryGetValue("TemplateCode", out object template) && template != null) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs new file mode 100644 index 000000000..49d33db37 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs @@ -0,0 +1,40 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class AliyunSmsVerifyCodeResponse +{ + /// + /// 请求状态码, OK代表请求成功 + /// + public string Code { get; set; } + /// + /// 状态码的描述 + /// + public string Message { get; set; } + /// + /// 请求是否成功 + /// + public bool Success { get; set; } + /// + /// 请求结果数据 + /// + public AliyunSmsVerifyCodeModel Model { get; set; } +} + +public class AliyunSmsVerifyCodeModel +{ + /// + /// 请求Id + /// + public string RequestId { get; set; } + /// + /// 业务Id + /// + public string BizId { get; set; } + /// + /// 外部流水号 + /// + public string OutId { get; set; } + /// + /// 验证码, 仅当使用阿里云短信验证服务生成验证码时携带 + /// + public string VerifyCode { get; set; } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs new file mode 100644 index 000000000..f9b61feae --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Sms.Aliyun; +/// +/// 阿里云发送短信验证码接口 +/// +public interface IAliyunSmsVerifyCodeSender +{ + /// + /// 发送短信验证码 + /// + /// + /// + Task SendAsync(SmsVerifyCodeMessage message); +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs new file mode 100644 index 000000000..745c9947f --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs @@ -0,0 +1,88 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class SmsVerifyCodeMessage +{ + /// + /// 方案名称,如果不填则为“默认方案”。最多不超过 20 个字符。 + /// + public string SchemeName { get; set; } + /// + /// 号码国家编码。默认为 86,目前也仅支持中国国内号码发送。 + /// + public string CountryCode { get; set; } + /// + /// 上行短信扩展码。上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。 + /// + public string SmsUpExtendCode { get; set; } + /// + /// 外部流水号。 + /// + public string OutId { get; set; } + /// + /// 验证码长度支持 4~8 位长度,默认是 4 位。 + /// + public long? CodeLength { get; set; } + /// + /// 验证码有效时长,单位秒,默认为 300 秒。 + /// + public long? ValidTime { get; set; } + /// + /// 核验规则,当有效时间内对同场景内的同号码重复发送验证码时,旧验证码如何处理。 + /// + /// + /// 1 - 覆盖处理(默认),即旧验证码会失效掉。
+ /// 2 - 保留,即多个验证码都是在有效期内都可以校验通过。 + ///
+ public long? DuplicatePolicy { get; set; } + /// + /// 时间间隔,单位:秒。即多久间隔可以发送一次验证码,用于频控,默认 60 秒。 + /// + public long? Interval { get; set; } + /// + /// 生成的验证码类型。当参数 TemplateParam 传入占位符时,此参数必填,将由系统根据指定的规则生成验证码。 + /// + /// + /// 1 - 纯数字(默认)。 + /// 2 - 纯大写字母。 + /// 3 - 纯小写字母。 + /// 4 - 大小字母混合。 + /// 5 - 数字+大写字母混合。 + /// 6 - 数字+小写字母混合。 + /// 7 - 数字+大小写字母混合。 + /// + public long? CodeType { get; set; } + /// + /// 是否返回验证码。 + /// + public bool? ReturnVerifyCode { get; set; } + /// + /// 是否自动替换签名重试(默认开启。 + /// + public bool? AutoRetry { get; set; } + /// + /// 短信接收方手机号。 + /// + public string PhoneNumber { get; } + /// + /// 签名名称。暂不支持使用自定义签名,请使用系统赠送的签名。 + /// + public string SignName { get; } + /// + /// 短信模板 CODE。参数SignName选择赠送签名时,必须搭配赠送模板下发短信。您可在赠送模板配置页面选择适用您业务场景的模板。 + /// + public string TemplateCode { get; } + /// + /// 短信模板参数。 + /// + public SmsVerifyCodeMessageParam TemplateParam { get; } + public SmsVerifyCodeMessage( + string phoneNumber, + SmsVerifyCodeMessageParam templateParam, + string signName = null, + string templateCode = null) + { + PhoneNumber = phoneNumber; + TemplateParam = templateParam; + SignName = signName; + TemplateCode = templateCode; + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs new file mode 100644 index 000000000..4158d44c6 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class SmsVerifyCodeMessageParam +{ + public string Code { get; } + public string Min { get; } + public SmsVerifyCodeMessageParam(string code, string min = "5") + { + Code = code; + Min = min; + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs index 19ea7986a..25fb01f07 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs @@ -1,4 +1,8 @@ -using LINGYUN.Abp.Tests; +using LINGYUN.Abp.Aliyun.Features; +using LINGYUN.Abp.Tests; +using LINGYUN.Abp.Tests.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; namespace LINGYUN.Abp.Aliyun @@ -8,5 +12,19 @@ namespace LINGYUN.Abp.Aliyun typeof(AbpTestsBaseModule))] public class AbpAliyunTestModule : AbpModule { + private const string UserSecretsId = "09233B21-9A8A-43A3-AA75-8D83C8A9537D"; + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + Configure(options => + { + options.Map(AliyunFeatureNames.Enable, (_) => "true"); + }); + } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs index 35078fdff..379ea4735 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.Aliyun; -using LINGYUN.Abp.Sms.Aliyun; +using LINGYUN.Abp.Aliyun.Features; +using LINGYUN.Abp.Tests.Features; using Volo.Abp.Modularity; namespace LINGYUN.Abp.Sms.Aliyun @@ -9,5 +10,12 @@ namespace LINGYUN.Abp.Sms.Aliyun typeof(AbpAliyunSmsModule))] public class AbpAliyunSmsTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Map(AliyunFeatureNames.Sms.Enable, (_) => "true"); + }); + } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs new file mode 100644 index 000000000..04e15c5d8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; +using Xunit; + +namespace LINGYUN.Abp.Sms.Aliyun; +public class AliyunSmsVerifyCodeSender_Tests : AbpAliyunTestBase +{ + protected IAliyunSmsVerifyCodeSender SmsSender { get; } + protected IConfiguration Configuration { get; } + + public AliyunSmsVerifyCodeSender_Tests() + { + SmsSender = GetRequiredService(); + Configuration = GetRequiredService(); + } + + /// + /// 阿里云短信测试 + /// + /// + /// + [Theory] + [InlineData("123456")] + public async Task Send_Test(string code) + { + var signName = Configuration["Aliyun:Sms:Sender:SignName"]; + var phone = Configuration["Aliyun:Sms:Sender:PhoneNumber"]; + var template = Configuration["Aliyun:Sms:Sender:TemplateCode"]; + + await SmsSender.SendAsync( + new SmsVerifyCodeMessage( + phone, + new SmsVerifyCodeMessageParam(code), + signName, + template)); + } +}