以前发短信使用过短信猫,现在,更多地是使用第三方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 Demo

            string serverIp = "api.ucpaas.com";
            string serverPort = "443";
            string account = "这里填写开发者的Account Sid";     //用户sid
            string token = "这里填写开发者的AuthToken";         //用户sid对应的token
            string 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());
            #endregion

            return 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@云之讯,等等♥。

在ASP.NET MVC下通过短信验证码注册的更多相关文章

  1. ASP.NET MVC+Bootstrap 实现短信验证

    短信验证大家都已经非常熟悉了,基本上每天都在接触手机短信的验证码,比方某宝,某东购物.站点注冊,网上银行等等,都要验证我们的手机号码真实性.这样做有什么优点呢. 曾经咱们在做站点的时候.为了提高用户注 ...

  2. 基于tp3.2的腾讯云短信验证码的实现

    新手小白在公司要完成短信验证码注册功能,最初由于没有经验,网上的教程又不是很全,便参考着官方API文档,进行开发 直接进入正题:使用composer下载腾讯云短信接口(记得添加依赖).在项目目录下新建 ...

  3. asp.net mvc 短信验证码

    把发短信功能写成一个类包,需要引用: SmsUtillity.cs: using System; using System.Collections.Generic; using System.Linq ...

  4. SNF快速开发平台MVC-EasyUI3.9之-ueditor富文本编辑在 asp.net MVC下使用步骤

    mvc项目中用到了这个富文本编辑就试着把遇到的问题个使用步骤在这里记录一下,希望大家少走弯路. 1.首先我们先下载net版本的uediot 2.然后把整个文档拷贝到我们的项目中,记得是整个 把下载的文 ...

  5. ASP.NET MVC下的四种验证编程方式[续篇]

    在<ASP.NET MVC下的四种验证编程方式>一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式("手工验证"."标注Validation ...

  6. ASP.NET MVC下的四种验证编程方式

    ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效性,我们将针对参数的验证成为Model绑定 ...

  7. Response.End()在Webform和ASP.NET MVC下的表现差异

    前几天在博问中看到一个问题--Response.End()后,是否停止执行?MVC与WebForm不一致.看到LZ的描述后,虽然奇怪于为何用Response.End()而不用return方式去控制流程 ...

  8. ASP.NET MVC下的四种验证编程方式[续篇]【转】

    在<ASP.NET MVC下的四种验证编程方式> 一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式(“手工验证”.“标注ValidationAttribute特性”.“ ...

  9. ASP.NET MVC下的四种验证编程方式【转】

    ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效 性,我们将针对参数的验证成为Model绑 ...

随机推荐

  1. 从xtrabackup备份恢复单表【转】

    目前对MySQL比较流行的备份方式有两种,一种上是使用自带的mysqldump,另一种是xtrabackup,对于数据时大的环境,普遍使用了xtrabackup+binlog进行全量或者增量备份,那么 ...

  2. shell脚本练习【转】

    1.写一个脚本,判断当前系统上所有用户的shell是否为可登录shell(即用户的shell不是/sbin/nologin):分别这两类用户的个数:通过字符串比较来实现: #脚本内容 [root@ce ...

  3. 001_docker-compose构建elk环境

    由于打算给同事分享elk相关的东西,搭建配置elk环境太麻烦了,于是想到了docker.docker官方提供了docker-compose编排工具,elk集群一键就可以搞定,真是兴奋.好了下面咱们开始 ...

  4. node.js express开发web问题

    1.新建的layout.ejs,在里面使用了<%= title %>,但是在运行时提示title is not defined. 将title改为<%= locals.title % ...

  5. Carbon 的 diffForHumans 方法

    Carbon 是继承自 PHP DateTime 类 的子类,但比后者提供了更加丰富.更加语义化的 API.其中一个比较实用的 API 就是 diffForHumans 方法,几乎每个用 Larave ...

  6. BZOJ 1305 dance跳舞(最大流+二分答案)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1305 解题思路:转自:https://blog.csdn.net/u012288458/ ...

  7. MySQL的架构模型

    看到大牛用户DB架构部的Keithlan<数据库性能优化之查询优化>,在学习过程发现很多不错的东西,就把它保存下来,分享给大家,因为作者说了一句很经典的话:“if you want to ...

  8. 程序设计实习MOOC / 程序设计与算法(三)第二周测验

    6. 学生信息处理程序 总时间限制: 1000ms 内存限制: 1024kB 描述 实现一个学生信息处理程序,计算一个学生的四年平均成绩. 要求实现一个代表学生的类,并且类中所有成员变量都是[私有的] ...

  9. 【LOJ】#2107. 「JLOI2015」城池攻占

    题解 用一个平衡树维护能攻占到u点的骑士,合并到父亲的时候去掉攻击力小于父亲生命值的那部分,只要把那棵树拆掉并且将树中的所有骑士更新一下答案,用无旋式treap很好写 合并的时候只要启发式合并就可以了 ...

  10. 2018年东北农业大学春季校赛 C-wyh的商机

    一天,你们wyh学长和你们zhl学长玩一个游戏,这个游戏规则是这样的 给你n个城市,保证这n个城市之间都只有一条道路可以到达. 有一件物品,在所有城市中都是一样的,但是由于各个城市的经济发展不同,导致 ...