ABP框架中短訊發送處理,包括阿里雲短訊和普通短訊商的短訊發送集成
在一般的系統中,往往也有短訊模塊的需求,如動態密碼的登錄,系統密碼的找回,以及為了獲取用戶手機號碼的短訊確認等等,在ABP框架中,本身提供了對郵件、短訊的基礎支持,那麼只需要根據自己的情況實現對應的接口即可。本篇隨筆介紹ABP框架中短訊發送處理,包括阿里雲短訊和普通短訊商的短訊發送集成。
1、基於第三方阿里雲短訊的實現
阿里雲短訊的實現,GitHub上也有一些人實現了一些模塊,我們只需要使用對應的模塊,然後在Core模塊中配置一下依賴即可。
我們一般在做某件事情的時候,先去看看別人是否已經做好了,使用它或者參考它來做事情是個不錯的思路。
基於這個道理,我們可以在VS的Nuget包管理中查找一下基於ABP的阿里雲短訊,可以找到一個合適的進行參考。
這個阿里雲的ABP實現適合我們當前的ABP框架版本,因此使用它即可,因此安裝引入對應的類庫在Core項目中。
在網站//github.com/tangyanglai/Sms.Core 我們看到它的使用過程,引入後在項目中啟動模塊依賴中添加對應的代碼即可。
[DependsOn(typeof(AliyunSmsModule))]
那麼我們在項目中的代碼如下所示
默認支持兩種配置方式,配置文件和SettingManager。下面以配置文件為例,格式為:
{ "AliyunSmsSettings": { "AccessKeyId": "", "AccessKeySecret": "", "SignName": "", //SendCodeAsync發送驗證碼使用 "TemplateCode": "" , //SendCodeAsync發送驗證碼使用 } }
根據上面的說明,我們在Host項目的AppSettings.json中增加對應的阿里雲配置項,如下所示。
其中AccessKeyId是標識用戶身份的ID,AccessKeySecret 是秘鑰,SigName是我們申請的短訊商戶簽名,TemplateCode是我們驗證碼的配置
而短訊一般是基於某個模板進行發送的,因此需要確定系統使用的短訊模板。
阿里雲的發送模塊是使用ISmsTemplateSender進行發送的,因此在代碼中使用如下所示。
那麼在使用發送短訊驗證碼的地方,如AccountService應用層中,使用的時候使用它的注入接口即可發送短訊驗證碼了。
使用發送短訊的操作如下所示。
/// <summary> /// 發送短訊驗證碼 /// </summary> /// <param name="phone">手機號碼</param> /// <param name="code">驗證碼</param> /// <returns></returns> public async Task<SmsResult> SendCodeAsync(string phone, string code) { return await _smsTemplateSender.SmsService.SendCodeAsync(phone, code); } /// <summary> /// 發送模板消息 /// </summary> /// <param name="input">模板對象</param> /// <returns></returns> public async Task<SmsResult> SendTemplateMessageAsync(SendTemplateMessageInput input) { return await _smsTemplateSender.SmsService.SendTemplateMessageAsync(input); }
2、使用自己的阿里雲短訊發送封裝
我之前隨筆《使用阿里雲的短訊服務發送短訊》中寫過如何處理阿里雲短訊,雖然那個是常規.net framework的程序中集成的,不過在.net Core的代碼都是差不多的。
我們知道ABP框架提供了對應的短訊發送接口,一般注入在系統中使用即可。
namespace MyProject.Net { /// <summary> /// 短訊發送接口 /// </summary> public interface ISmsSender { Task<CommonResult> SendAsync(string number, string message); } }
那麼我們自己定義的短訊發送接口,實現它即可,然後注入使用對應的接口即可。
根據阿里雲接口需求,定義一個類似的模型用作加載參數的。
/// <summary> /// 阿里雲配置參數 /// </summary> internal class AliyunSmsSettting { public string AccessKeyId { get; set; } public string AccessKeySecret { get; set; } public string RegionId { get; set; } public string EndpointName { get; set; } public string Domain { get; set; } public string Product { get; set; } public string SignName { get; set; } public string TemplateCode { get; set; } public string TemplateParam { get; set; } }
然後讓我們的接口實現函數,初始化的時候獲取對應的配置信息供使用。
{ /// <summary> /// 使用簡單封裝,不依賴其他外部模塊的阿里雲短訊發送 /// </summary> public class AliyunSmsSender : IShouldInitialize, ISmsSender, ITransientDependency { public IConfiguration AppConfiguration { get; set; } public IIocManager IocManager { get; set; } public ILogger Logger { get; set; } private const string Key = "AliyunSmsSettings"; private const string endpoint = "dysmsapi.aliyuncs.com"; /// <summary> /// 短訊配置信息 /// </summary> private AliyunSmsSettting SmsSettings { get; set; } public AliyunSmsSender(IConfiguration appConfiguration, IIocManager iocManager) { this.AppConfiguration = appConfiguration; this.IocManager = iocManager; this.Logger = NullLogger.Instance; } public void Initialize() { this.SmsSettings = GetConfigFromConfigOrSettingsByKey<AliyunSmsSettting>().Result; }
然後根據我之前隨筆的實現邏輯,給他實現對應的發送操作即可,部分關鍵代碼如下所示
/// <summary> /// 發送短訊 /// </summary> /// <param name="number">手機號碼</param> /// <param name="message">消息或驗證碼</param> /// <returns></returns> public async Task<CommonResult> SendAsync(string number, string message) { var result = await PrivateSend(number, message); return result; } /// <summary> /// 發送邏輯 /// </summary> /// <returns></returns> private async Task<CommonResult> PrivateSend(string number, string code) { string nowDate = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'");//GTM時間 var keyValues = new Dictionary<string, string>();//聲明一個字典 //1.系統參數 keyValues.Add("SignatureMethod", "HMAC-SHA1"); keyValues.Add("SignatureNonce", Guid.NewGuid().ToString()); keyValues.Add("AccessKeyId", this.SmsSettings.AccessKeyId); keyValues.Add("SignatureVersion", "1.0"); keyValues.Add("Timestamp", nowDate); keyValues.Add("Format", "Json");//可換成xml //2.業務api參數 keyValues.Add("Action", "SendSms"); keyValues.Add("Version", "2017-05-25"); keyValues.Add("RegionId", "cn-hangzhou"); keyValues.Add("PhoneNumbers", number); keyValues.Add("SignName", this.SmsSettings.SignName); keyValues.Add("TemplateCode", this.SmsSettings.TemplateCode); keyValues.Add("TemplateParam", string.Format("{{\"code\":\"{0}\"}}", code)); keyValues.Add("OutId", "123"); //3.去除簽名關鍵字key if (keyValues.ContainsKey("Signature")) { keyValues.Remove("Signature"); } //4.參數key排序 Dictionary<string, string> ascDic = keyValues.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value.ToString()); //5.構造待簽名的字符串 var builder = new StringBuilder(); foreach (var item in ascDic) { if (item.Key == "SignName") { } else { builder.Append("&").Append(specialUrlEncode(item.Key)).Append("=").Append(specialUrlEncode(item.Value)); } if (item.Key == "RegionId") { builder.Append("&").Append(specialUrlEncode("SignName")).Append("=").Append(specialUrlEncode(keyValues["SignName"])); } } string sorteQueryString = builder.ToString().Substring(1); StringBuilder stringToSign = new StringBuilder(); stringToSign.Append("GET").Append("&"); stringToSign.Append(specialUrlEncode("/")).Append("&"); stringToSign.Append(specialUrlEncode(sorteQueryString)); string Sign = MySign(this.SmsSettings.AccessKeySecret + "&", stringToSign.ToString()); //6.簽名最後也要做特殊URL編碼 string signture = specialUrlEncode(Sign); //最終打印出合法GET請求的URL string url = string.Format("//{0}/?Signature={1}{2}", endpoint, signture, builder); var modal = await GetHtmlResult(url); return new CommonResult(modal.Success, modal.Message); }
然後在Core模塊中初始化的時候,替換對應的短訊發送實現即可。
這樣就可以使用我們自己的短訊接口了
發送代碼如下所示
/// <summary> /// 發送短訊驗證碼 /// </summary> /// <param name="phone">手機號碼</param> /// <param name="code">驗證碼</param> /// <returns></returns> public async Task<CommonResult> SendSmsCodeAsync(string phone, string code) { return await _smsSender.SendAsync(phone, code); //使用阿里雲接口 }
3、普通短訊商的短訊發送集成
還有一種我們可能不是基於阿里雲,而是其他提供商的接口發送,操作也是自定義短訊接口的封裝。
我們使用如下參數來確定短訊提供商的信息,也可以根據需要自己調整。
定義一個配置對應的配置對象,方便獲取參數信息。
/// <summary> /// 自定義短訊配置 /// </summary> internal class MySmsSettings { /// <summary> /// 供應商代碼 /// </summary> public string spcode { get; set; } /// <summary> /// 賬戶 /// </summary> public string username { get; set; } /// <summary> /// 密碼 /// </summary> public string password { get; set; } }
由於我們這個的實現也是基於標準接口ISmsSender的,那麼我們實現這個後,也需要特定指定這個實現為ISmsSender的使用。
例如在CoreModule中替換為這個短訊實現的話,如下代碼。
//使用自定義的 ISmsSender Configuration.ReplaceService<ISmsSender, MySmsSender>();
使用接口發送短訊的時候,就和我們上面的操作類似的了。