在ASP.NET MVC下通过短信验证码注册
以前发短信使用过短信猫,现在,更多地是使用第三方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@云之讯,等等♥。