1、手动创建
2、命令行从模板创建
手动创建就是复制一个官方的任意模块。
这个不细说。
2、我是从命令行创建的。 首先要安装orchard core的模板
dotnet new install OrchardCore.ProjectTemplates::2.0.2
参考: https://docs.orchardcore.net/en/latest/getting-started/templates/
在自己项目的文件夹 运行cmd命令 ,然后执行创建一个新的模块 : Super.Aliyun.SMS
模板可以选择 ocmodulecms 跟ocmodulemvc 前者是cms的模块,后者是mvc的。 cms自带一些基础的路由
然后在vs的项目 添加该模块,以下是创建的默认文件:
可以修改 Manifest.cs (清单)的内容,比如 作者,还有功能名称。
默认的访问路径是: 模块名称/控制器/方法
我这边创建的是 Super.Aliyun.SMS
所以默认是
http://localhost:5111/Super.Aliyun.SMS/Home/Index
现在开始。
引入orchardcore的sms模块
接下去只要实现阿里云的sms服务,按官方的说明:
要实现自己想要的
services.AddSmsProviderOptionsConfiguration<YourCustomImplemenation>()
`public class TwilioProviderOptionsConfigurations : IConfigureOptions
{
private readonly ISiteService _siteService;
public TwilioProviderOptionsConfigurations(ISiteService siteService)
{
_siteService = siteService;
}
public void Configure(SmsProviderOptions options)
{
var typeOptions = new SmsProviderTypeOptions(typeof(TwilioSmsProvider));
var site = _siteService.GetSiteSettingsAsync().GetAwaiter().GetResult();
var settings = site.As<TwilioSettings>();
typeOptions.IsEnabled = settings.IsEnabled;
options.TryAddProvider(TwilioSmsProvider.TechnicalName, typeOptions);
}
}所以我们要添加 阿里云短信的相关配置。
public class AliyunProviderOptionsConfigurations : IConfigureOptions
{
private readonly ISiteService _siteService;
public AliyunProviderOptionsConfigurations(ISiteService siteService)
{
_siteService = siteService;
}
public void Configure(SmsProviderOptions options)
{
var typeOptions = new SmsProviderTypeOptions(typeof(TwilioSmsProvider));
var site = _siteService.GetSiteSettingsAsync().GetAwaiter().GetResult();
var settings = site.As<TwilioSettings>();
typeOptions.IsEnabled = settings.IsEnabled;
options.TryAddProvider(TwilioSmsProvider.TechnicalName, typeOptions);
}
}添加AliyunProviderOptionsConfigurations
ublic class AliyunProviderOptionsConfigurations : IConfigureOptions
{
private readonly ISiteService _siteService;
public AliyunProviderOptionsConfigurations(ISiteService siteService)
{
_siteService = siteService;
}
public void Configure(SmsProviderOptions options)
{
var typeOptions = new SmsProviderTypeOptions(typeof(AliyunSmsProvider));
var settings = _siteService.GetSettingsAsync<AliyunSettings>()
.GetAwaiter()
.GetResult();
typeOptions.IsEnabled = settings.IsEnabled;
options.TryAddProvider(AliyunSmsProvider.TechnicalName, typeOptions);
}
}`
添加aliyunsettings 跟aliyunsmsprovider
` public class AliyunSmsProvider : ISmsProvider
{
public const string TechnicalName = "Aliyun";
public const string ProtectorName = "Aliyun";
private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions {
PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance
};
private readonly ISiteService _siteService;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly ILogger<AliyunSmsProvider> _logger;
private readonly IHttpClientFactory _httpClientFactory;
protected readonly IStringLocalizer S;
private AliyunSettings _settings;
public LocalizedString Name => S["Aliyun"];
public AliyunSmsProvider(ISiteService siteService, IDataProtectionProvider dataProtectionProvider, ILogger<AliyunSmsProvider> logger, IHttpClientFactory httpClientFactory, IStringLocalizer<AliyunSmsProvider> stringLocalizer)
{
_siteService = siteService;
_dataProtectionProvider = dataProtectionProvider;
_logger = logger;
_httpClientFactory = httpClientFactory;
S = stringLocalizer;
}
public async Task<SmsResult> SendAsync(SmsMessage message)
{
ArgumentNullException.ThrowIfNull(message, "message");
if (string.IsNullOrEmpty(message.To)) {
throw new ArgumentException("A phone number is required in order to send a message.");
}
if (string.IsNullOrEmpty(message.Body)) {
throw new ArgumentException("A message body is required in order to send a message.");
}
try {
AliyunSettings aliyunSettings = await GetSettingsAsync();
var aliyunclient = CreateClient(aliyunSettings);
//var response = await aliyunclient.SendSmsAsync(request);
SendSmsRequest sendSmsRequest = new SendSmsRequest {
PhoneNumbers = message.To,
SignName = aliyunSettings.SignName,
TemplateCode = aliyunSettings.TemplateCode,
TemplateParam = message.Body
};
var response = await aliyunclient.SendSmsAsync(sendSmsRequest);
if (response.StatusCode == 200) {
return SmsResult.Success;
} else {
_logger.LogError("Aliyun SMS service was unable to send SMS messages. Error code: {errorCode}, message: {errorMessage}", response.StatusCode, response.Body);
return SmsResult.Failed(S["SMS message was not send."]);
}
} catch (Exception ex) {
_logger.LogError(ex, "Twilio service was unable to send SMS messages.");
return SmsResult.Failed(S["SMS message was not send. Error: {0}", new object[1] { ex.Message }]);
}
// return SmsResult.Failed(S["SMS message was not send. Error: {0}", new object[1] { "未配置" }]);
}
/// <summary>
/// 返回请求客户端 根据具体的实现
/// </summary>
/// <param name="settings"></param>
/// <returns></returns>
private HttpClient GetHttpClient(AliyunSettings settings)
{
string s = settings.AccessKeyId + ":" + settings.AccessKeySecret;
string parameter = Convert.ToBase64String(Encoding.ASCII.GetBytes(s));
HttpClient httpClient = _httpClientFactory.CreateClient("Aliyun");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", parameter);
return httpClient;
}
public static Client CreateClient(AliyunSettings settings)
{
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378671.html。
AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config {
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
AccessKeyId = settings.AccessKeyId,
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
AccessKeySecret = settings.AccessKeySecret,
};
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
config.Endpoint = "dysmsapi.aliyuncs.com";
return new AlibabaCloud.SDK.Dysmsapi20170525.Client(config);
}
/// <summary>
/// 获取设置
/// </summary>
/// <returns></returns>
private async Task<AliyunSettings> GetSettingsAsync()
{
if (_settings == null) {
AliyunSettings aliyunSettings = await _siteService.GetSettingsAsync<AliyunSettings>();
IDataProtector protector = _dataProtectionProvider.CreateProtector("Aliyun");
_settings = new AliyunSettings {
AccessKeyId = aliyunSettings.AccessKeyId,
AccessKeySecret = aliyunSettings.AccessKeySecret,
SignName = aliyunSettings.SignName,
TemplateCode= aliyunSettings.TemplateCode,
};
}
return _settings;
}
}对应添加 driver 还有视图
public class AliyunSettingsDisplayDriver : SiteDisplayDriver
{
private readonly IShellReleaseManager _shellReleaseManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly IPhoneFormatValidator _phoneFormatValidator;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly INotifier _notifier;
internal readonly IHtmlLocalizer H;
internal readonly IStringLocalizer S;
protected override string SettingsGroupId
=> SmsSettings.GroupId;
public AliyunSettingsDisplayDriver(
IShellReleaseManager shellReleaseManager,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IPhoneFormatValidator phoneFormatValidator,
IDataProtectionProvider dataProtectionProvider,
INotifier notifier,
IHtmlLocalizer<AliyunSettingsDisplayDriver> htmlLocalizer,
IStringLocalizer<AliyunSettingsDisplayDriver> stringLocalizer)
{
_shellReleaseManager = shellReleaseManager;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_phoneFormatValidator = phoneFormatValidator;
_dataProtectionProvider = dataProtectionProvider;
_notifier = notifier;
H = htmlLocalizer;
S = stringLocalizer;
}
public override IDisplayResult Edit(ISite site, AliyunSettings settings, BuildEditorContext c)
{
return Initialize<AliyunSettingsViewModel>("AliyunSettings_Edit", model => {
model.IsEnabled = settings.IsEnabled;
model.AccessKeyId = settings.AccessKeyId;
model.AccessKeySecret = settings.AccessKeySecret;
model.SignName = settings.SignName;
model.TemplateCode = settings.TemplateCode;
}).Location("Content:5#Aliyun")
.RenderWhen(() => _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext?.User, SmsPermissions.ManageSmsSettings))
.OnGroup(SettingsGroupId);
}
public override async Task<IDisplayResult> UpdateAsync(ISite site, AliyunSettings settings, UpdateEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;
if (!await _authorizationService.AuthorizeAsync(user, SmsPermissions.ManageSmsSettings)) {
return null;
}
var model = new AliyunSettingsViewModel();
await context.Updater.TryUpdateModelAsync(model, Prefix);
var hasChanges = settings.IsEnabled != model.IsEnabled;
var smsSettings = site.As<SmsSettings>();
if (!model.IsEnabled) {
if (hasChanges && smsSettings.DefaultProviderName == TwilioSmsProvider.TechnicalName) {
await _notifier.WarningAsync(H["You have successfully disabled the default SMS provider. The SMS service is now disable and will remain disabled until you designate a new default provider."]);
smsSettings.DefaultProviderName = null;
site.Put(smsSettings);
}
settings.IsEnabled = false;
} else {
settings.IsEnabled = true;
if (string.IsNullOrWhiteSpace(model.AccessKeyId)) {
context.Updater.ModelState.AddModelError(Prefix, nameof(model.AccessKeyId), S["accessKeyId requires a value."]);
}
if (string.IsNullOrWhiteSpace(model.AccessKeySecret)) {
context.Updater.ModelState.AddModelError(Prefix, nameof(model.AccessKeySecret), S["AccessKeySecret requires a value."]);
}
if (settings.SignName == null && string.IsNullOrWhiteSpace(model.SignName)) {
context.Updater.ModelState.AddModelError(Prefix, nameof(model.SignName), S["SignName required a value."]);
}
// Has change should be evaluated before updating the value.
hasChanges |= settings.AccessKeyId != model.AccessKeyId;
hasChanges |= settings.AccessKeySecret != model.AccessKeySecret;
hasChanges |= settings.SignName != model.SignName;
hasChanges |= settings.TemplateCode != model.TemplateCode;
settings.AccessKeyId = model.AccessKeyId;
settings.AccessKeySecret = model.AccessKeySecret;
settings.SignName = model.SignName;
settings.TemplateCode = model.TemplateCode;
}
if (context.Updater.ModelState.IsValid && settings.IsEnabled && string.IsNullOrEmpty(smsSettings.DefaultProviderName)) {
// If we are enabling the only provider, set it as the default one.
smsSettings.DefaultProviderName = AliyunSmsProvider.TechnicalName;
site.Put(smsSettings);
hasChanges = true;
}
if (hasChanges) {
_shellReleaseManager.RequestRelease();
}
return Edit(site, settings, context);
}
}`
`@using OrchardCore.Sms.Services
@using OrchardCore.Sms.ViewModels
@using OrchardCore.Sms
@using Super.Aliyun.SMS.ViewModels
@model AliyunSettingsViewModel
<h4>@T["Aliyun Account Info"]</h4>
<div class="mb-3" asp-validation-class-for="AccessKeyId">
<label asp-for="AccessKeyId" class="form-label">@T["AccessKeyId"]</label>
<input asp-for="AccessKeyId" class="form-control" />
<span asp-validation-for="AccessKeyId"></span>
<span class="hint">@T["AccessKeyId must include a country code."]</span>
</div>
<div class="mb-3" asp-validation-class-for="AccessKeySecret">
<label asp-for="AccessKeySecret" class="form-label">@T["AccessKeySecret"]</label>
<input asp-for="AccessKeySecret" class="form-control" />
<span asp-validation-for="AccessKeySecret"></span>
<span class="hint">@T["AccessKeySecret must include a country code."]</span>
</div>
<div class="mb-3" asp-validation-class-for="SignName">
<label asp-for="SignName" class="form-label">@T["SignName"]</label>
<input asp-for="SignName" class="form-control" />
<span asp-validation-for="SignName"></span>
</div>
<div class="mb-3" asp-validation-class-for="TemplateCode">
<label asp-for="TemplateCode" class="form-label">@T["TemplateCode"]</label>
<input asp-for="TemplateCode" class="form-control" />
<span asp-validation-for="TemplateCode"></span>
</div>
修改starup.cs 添加
` public override void ConfigureServices(IServiceCollection services)
{
services.AddSmsProvider<AliyunSmsProvider>("Aliyun");
services.AddSmsProviderOptionsConfiguration<AliyunProviderOptionsConfigurations>()
.AddSiteDisplayDriver<AliyunSettingsDisplayDriver>(); ;
}`
web项目添加引用, 然后运行 添加功能
配置下双重验证。
在用户注册的时候 就会要求填写验证码
第一次的时候 会贴心的提供恢复码