ABP框架中簡訊傳送處理,包括阿里雲簡訊和普通簡訊商的簡訊傳送整合
在一般的系統中,往往也有簡訊模組的需求,如動態密碼的登入,系統密碼的找回,以及為了獲取使用者手機號碼的簡訊確認等等,在ABP框架中,本身提供了對郵件、簡訊的基礎支援,那麼只需要根據自己的情況實現對應的介面即可。本篇隨筆介紹ABP框架中簡訊傳送處理,包括阿里雲簡訊和普通簡訊商的簡訊傳送整合。
1、基於第三方阿里雲簡訊的實現
阿里雲簡訊的實現,GitHub上也有一些人實現了一些模組,我們只需要使用對應的模組,然後在Core模組中配置一下依賴即可。
我們一般在做某件事情的時候,先去看看別人是否已經做好了,使用它或者參考它來做事情是個不錯的思路。
基於這個道理,我們可以在VS的Nuget包管理中查詢一下基於ABP的阿里雲簡訊,可以找到一個合適的進行參考。
這個阿里雲的ABP實現適合我們當前的ABP框架版本,因此使用它即可,因此安裝引入對應的類庫在Core專案中。
在網站https://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("http://{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>();
使用介面傳送簡訊的時候,就和我們上面的操作類似的了。
&n