以前發短信使用過短信貓,現在,更多地是使用第三方API。大致過程是:
→ 用戶在頁面輸入手機號碼
→ 用戶點擊"獲取驗證碼"按鈕,把手機號碼發送給服務端,服務端產生幾位數的隨機碼,並保存在某個地方(Session, Applicaiton, 數據庫, 等等),調用第三方的API
→ 第三方發送幾位數的隨機碼至用戶手機
→ 用戶在頁面輸入接收到的隨機碼
→ 把隨機碼等發送給服務端,與服務端保存的隨機碼比較,如果一致,就通過,讓注冊
就按如下界面來說吧:
我們需要考慮的方面包括:
● 手機號碼:判斷手機號碼的合法性,與數據庫中已有的手機號碼比較,判斷是否有重復,等等
● "獲取短信驗證碼"按鈕:點擊后,禁用它,再來一個比如60秒的倒計時,倒計時結束恢復使用
● "提交"按鈕:在提交之前需要判斷表單是否驗證通過,以及驗證碼是否通過
● 點擊"獲取短信驗證碼"按鈕的次數:比如,需要限制來自同一個IP,每天只能點擊這個按鈕3次
選擇驗證碼提供方、前期准備
本人選擇了"雲之訊":http://www.ucpaas.com/
注冊成為"雲之訊"的用戶。
進入"雲之訊"管理后台,首頁就可看到開發者信息,包括Account Sid, AuthToken, Rest URL,這些將來都會用到。
依次點擊"應用管理","應用列表",右側頁面的"創建應用",創建成功后將會分配到一個應用ID,這個應用ID也會被用到。另外,創建的應用需要"雲之訊"審核通過后,才可以在本地調試。
依次點擊"應用管理","短信管理",右側頁面的"添加模版", 在"添加模版"頁,"內容"這項應該類似這樣填寫:您注冊{1}網站的驗證碼為{2},請於{3}分鍾內正確輸入驗證碼,添加短信模版成功后會分配到一個模版ID,這個模版ID也會被用到,創建的短信模版也需要"雲之訊"審核通過后,才可以在本地調試。
好了,再來總結一下,我們在開發的時候需要哪些信息。包括:
● Rest URL
● Account Sid
● AuthToken
● 應用ID
● 短信模版ID
● 短息模版的內容,比如"您注冊{1}網站的驗證碼為{2},請於{3}分鍾內正確輸入驗證碼",{1},{2},{3}是占位符,在應用程序中我們只需要拼接出一個以英文逗號隔開的字符串就可以,比如"我的網站,我的驗證碼,1"
先下載一個C#的Demo,在這里下載:http://www.ucpaas.com/product_service/download, 在"REST Server Demo"中可以找到。
另外,"雲之訊"C#版Demo是使用HttpWebRequest,向API發出請求的,在ASP.NET MVC中,我們也可以使用HttpClient向API發出請求,請求格式參考如下:
http://docs.ucpaas.com/doku.php?id=rest_api%E4%BB%8B%E7%BB%8D
http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1%E9%AA%8C%E8%AF%81%E7%A0%81_%E6%A8%A1%E6%9D%BF%E7%9F%AD%E4%BF%A1
在開發的時候,有時需要查看返回的狀態碼,查看這里:http://docs.ucpaas.com/doku.php?id=rest_error
在ASP.NET MVC下開發
對於用戶注冊相關的視圖模型,給出如下一個類。
public class UserInputVm{[Required(ErrorMessage = "必填")][StringLength(16, ErrorMessage = "長度1-16位")][Display(Name = "請輸入手機號")][RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手機號格式不正確")]public string LoginName { get; set; }}
以上,只考慮了手機輸入的合法性,現實中,還需要判斷用戶輸入的手機號是否和數據庫中已有的重復,用到一個遠程驗證特性,參考這里:http://www.cnblogs.com/darrenji/p/3578133.html
在項目根目錄下創建一個"Extension"文件夾,並創建一個UCSRestRequest類,把"雲之訊"C#版Demo中的UCSRestRequest類下的內容以及EBodyType枚舉一同拷貝下來。
在HomeController下,Index方法送出一個視圖模型實例。
public class HomeController : Controller{public ActionResult Index(){return View(new UserInputVm());}......}
在Home/Index.cshtml視圖中,點擊"發送驗證碼"按鈕,讓該按鈕倒計時,並把手機號發送給服務端;點擊"提交"按鈕,判斷驗證表單通過之后,再向服務端發送用戶填寫的驗證碼,驗證碼通過之后才提交表單信息。
@model MvcApplication2.Models.UserInputVm@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";}<h2>Index</h2>@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "addForm" })){@Html.LabelFor(m => m.LoginName)@Html.TextBoxFor(m => m.LoginName)@Html.ValidationMessageFor(m => m.LoginName)<span id="success"></span><br /><br /><span>請輸入驗證碼</span><input type="text" id="myCode" /><span id="codehint"></span><input type="button" id="getCode" value="點擊獲取驗證碼" /><br /><br /><input type="button" id="up" value="提交" />}@section scripts{<script type="text/javascript">$(function () {//點擊發送驗證碼$('#getCode').on("click", function () {checkGetCodeBtn();$.post('@Url.Action("GetCheckNum", "Home")', { 'phoneNum': $('#LoginName').val() }, function (data) {if (data.msg) {$('#success').text("已發送驗證碼");alert(data.content);} else {var $getCodeBtn = $('#getCode');clearInterval(t);$getCodeBtn.prop('disabled', false);$getCodeBtn.val("點擊獲取驗證碼");count = 60;$('#success').text("");}});});//提交$('#up').on("click", function () {//表單驗證通過才驗證驗證碼的正確性if ($('#addForm').valid()) {//clearInterval(t);$.post('@Url.Action("CheckCode", "Home")', { 'checkNum': $('#myCode').val() }, function (data) {if (data.msg) {//驗證碼匹配alert(data.content);$.ajax({cache: false,url: '@Url.Action("Index", "Home")',type: 'POST',dataType: 'json',data: $('#addForm').serialize(),success: function (result) {if (result.msg) {alert(result.content);}},error: function (xhr, status) {alert("提交失敗,狀態碼:" + status);}});} else {$('#codehint').text(data.content);}});}});});var count = 60; //計時開始var t; //時間間隔種子var isPass = false;//驗證碼是否輸入正確function checkGetCodeBtn() {//關於按鈕var $getCodeBtn = $('#getCode');t = setInterval(function () {$getCodeBtn.val(count + "秒之后重新獲取");$getCodeBtn.prop('disabled', true);count--;if (count == 0) {clearInterval(t);$getCodeBtn.prop('disabled', false);$getCodeBtn.val("點擊獲取驗證碼");count = 60;$('#success').text("");}}, 1000);}</script>}
再回到HomeController,有一個方法用來接收用戶的手機號,產生並保存隨機碼,調用API;有一個方法用來接收用戶的短信驗證碼,判斷是否匹配;當然還有一個接收表單數據的方法。
public class HomeController : Controller{......[HttpPost]public ActionResult Index(UserInputVm userInputVm){if (ModelState.IsValid){//實際上這里要做數據庫保存工作//return PartialView("DisplayUser", userInputVm);return Json(new {msg=true,content=userInputVm.LoginName});}else{return View(userInputVm);}}//讓api把驗證碼發給用戶手機[HttpPost]//[EnableThrottling(PerSecond = 1, PerMinute = 1, PerHour = 3, PerDay = 3)]public ActionResult GetCheckNum(string phoneNum){//TODO: 在這里再次判斷用戶手機號是否與數據庫中已有的重復//產生隨機驗證碼Random r = new Random();string temMsg = string.Empty;for (int i = 0; i < 4; i++)
{temMsg += r.Next(0, 9);}//保存隨機碼//Session["num"] = temMsg;//ControllerContext.HttpContext.Application["num"] = temMsg;//TODO: 這里建議隨機碼保存到數據庫,因為調用API發送短信,Session,Application狀態會丟失//拼接短信內容StringBuilder sb = new StringBuilder();sb.AppendFormat("{0},{1},{2}", "就依你", temMsg, "1"); //您注冊{1}網站的驗證碼為{2},請於{3}分鍾內正確輸入驗證碼#region Demostring serverIp = "api.ucpaas.com";string serverPort = "443";string account = "這里填寫開發者的Account Sid"; //用戶sidstring token = "這里填寫開發者的AuthToken"; //用戶sid對應的tokenstring appId = "這里填寫審核通過上線應用的Id"; //對應的應用id,非測試應用需上線使用string templatedId = "這里填寫短信模版ID"; //短信模板id,需通過審核//string clientNum = "60000000000001";//string clientpwd = "";//string friendName = "";//string clientType = "0";//string charge = "0";//string phone = "";//string date = "day";//uint start = 0;//uint limit = 100;//string toPhone = ""; //發送短信手機號碼,群發逗號區分//string param = ""; //短信參數 a,b,c//string verifyCode = "1234";//string fromSerNum = "4000000000";//string toSerNum = "4000000000";//string maxallowtime = "60";UCSRestRequest api = new UCSRestRequest();api.init(serverIp, serverPort);api.setAccount(account, token);api.enabeLog(true);api.setAppId(appId);api.enabeLog(true);string feedback = api.SendSMS(phoneNum, templatedId, sb.ToString());#endregionreturn Json(new { msg = true, content = feedback });}//檢測驗證碼[HttpPost]public ActionResult CheckCode(string checkNum){try{//實際需要從數據庫中獲取保存的隨機短信驗證碼//實際,這里的Application["num"]或Session["num"]數據狀態已經丟失了if (ControllerContext.HttpContext.Application["num"] != null){string temp = ControllerContext.HttpContext.Application["num"].ToString();if (checkNum == temp){return Json(new { msg = true, content = temp });}else{return Json(new { msg = false, content = "驗證碼不匹配請重新輸入" });}}else{return Json(new { msg = false, content = "驗證碼失效請重新獲取" });}}catch (Exception ex){throw;}}}
以上,
● [HttpPost]下的Index方法,用來接收保存表單數據。
● GetCheckNum方法接收用戶手機號,需要提醒的是:
1、EnableThrottling特性是用來限制在單位時間間隔之內,來自同一個IP請求的次數,使用方法參考這里:http://www.cnblogs.com/darrenji/p/4446767.html
2、不要把隨機短信驗證碼保存到Session或Application中,因為Demo中實際使用HttpWebRequest向API發出請求,另開了一個線程,Session或Application狀態會丟失,建議把隨機短信驗證碼保存到數據庫中。
3、雖然前端頁面中可以判斷用戶輸入手機號是否與數據庫中已有的重復,但用戶還是有可能把重復的手機號發送到服務端來,所以,在服務端的本方法內,還是有必要再次判斷用戶的手機號是否重復。
4、也可以不使用Demo中提供的方法向API發出請求,使用HttpClient同樣可以,不過需要字節拼接和設置,需要對HttpClient的使用有一定的了解。
● CheckCode方法用來接收用戶的短信驗證碼,建議把接收的驗證碼和數據庫中該手機號的驗證碼對比。
最后,♥謝謝UIT工作室推薦了"雲之訊"並提供了HttpClient調用的代碼參考,♥謝謝"雲之訊"團隊成員的配合,他/她們是:Cathy@雲之訊,Ada@雲之訊, Auspicious@雲之訊,等等♥。