本书未发布

43. 注册手机验证

未匹配的标注

说明

在本节里,我们将基于上节完成的短信发送功能验证用户注册手机是否有效。

需求分解

验证注册手机是否有效的具体实现流程如下:

  • 首先在表单的注册手机号码添加一个『发送短信』按钮,用户输入手机号码有效后点击该按钮发送验证短信。用户点击该按钮后 60 秒内不能再次点击;
  • 在表单里添加一个验证码字段,用户在该字段填写收到的六位数短信码,并使用 JQuery Validate 验证用户填写是否正确;
  • 如果 JQuery Validation 验证用户输入的短信验证码和其它字段全都正确,以 AJAX 方式提交表单到后端程序;
  • 后端 PHP 程序使用验证器再次验证短信码和其它字段,验证通过后后保存注册数据,否则给表单返回错误信息。

验证器

因为在数据模型里我们是使用验证器来验证表单数据是否合法,所以我们只需要在验证器里添加短信验证码是否正确即可,不需要修改数据模型方法。

application/common/validate/User.php

<?php

namespace app\common\validate;

use think\Validate;
use think\facade\Cache;

class User extends Validate
{
    protected $rule = [
        .
        .
        .
        'sms_code' => 'require|length:6|checkCode',
    ];

    protected $message = [
        .
        .
        .
        'sms_code.require' => '短信验证码不能为空'
        'sms_code.length' => '短信验证码不正确'
        'sms_code.filter_sms_code' => '短信验证码不正确'
    ];

    /**
     * 自定义验证方法-验证用户输入的短信验证码是否正确
     * @Author   zhanghong(Laifuzi)
     * @DateTime 2019-02-16
     * @param    string             $value 字段值
     * @param    string             $rule  字段值验证值
     * @param    array              $data  表单提交的所有数据
     * @return   string/true               验证结果
     */
    public function checkCode($value, $rule, $data = [])
    {
        $invalid_msg = '短信验证码不正确';
        if(!isset($data['mobile'])){
            return $invalid_msg;
        }

        $mobile = $data['mobile'];
        $cache_code = Cache::store('redis')->get($mobile);
        if(empty($cache_code)){
            return $invalid_msg;
        }else if($value != $cache_code){
            return $invalid_msg;
        }
        return true;
    }
}

代码解读

方法 checkCode 是我们自定义的判断短信验证码是否正确的规则方法。自定义验证规则时需要注意,当验证通过时返回值必须是 true

官方文档在 独立验证 这一节只是简单介绍了一下 自定义验证规则 。对照官方文档可以发现我们上面声明的规则方法接收三个参数—— $value$rule$data,比官方示例多了一个参数 $data。如果我们阅读一下 think\Validate 类的源码就会知道,验证规则方法接收两个或三个参数都正确,因为在该类里有些规则方法是接收三个参数,也有些规则方法是接收两个参数。

控制器

首先,我们需要在 Register 控制器里声明一个方法实现注册时给用户填写的注册手机发送验证码短信。

application/index/controller/Register.php

<?php
.
.
.
use app\common\model\Sms;

class Register extends Base
{
    .
    .
    .
    public function send_code(Request $request)
    {
        if(!$request->isAjax()){
            $this->redirect('[page.signup]');
        }else if(!$request->isPost()){
            $this->error('访问页面不存在');
        }

        $mobile = $request->post('mobile');
        if(empty($mobile)){
            $this->error('注册手机号码不能为空');
        }
        $param = ['name' => 'mobile', 'mobile' => $mobile];
        if(!User::checkFieldUnique($param)){
            $this->error('手机号码已注册');
        }

        try {
            $sms = new Sms();
            $sms->sendCode($mobile);
        } catch (\Exception $e) {
            $this->error($e->getMessage());
        }

        $this->success('验证码发送成功');
    }
}

在定义发送验证码短信时不要忘记在类声明前引入 app\common\model\Sms ,另外,因为我们是让用户点击按钮时调用该方法发送短信,所以这个方法只允许 AJAX Post 请求。

接下来,我们还需要声明一个控制( action )方法完成提交表单前验证用户填写的验证码是否正确。因为我们想让项目里其它短信验证也使用该方法,所以新创建一个控制器来声明该方法。

$ php think make:controller index/Verify

验证控制器代码如下:

application/index/controller/Verify.php

<?php

namespace app\index\controller;

use think\Request;
use app\common\validate\User as UserValidate;

class Verify extends Base
{
    public function valid_code(Request $request)
    {
        if(!$request->isAjax()){
            $this->redirect('[page.root]');
        }else if(!$request->isPost()){
            $this->success('访问页面不存在');
        }

        $is_valid = false;

        $validate = new UserValidate();
        $param = $request->post();
        if(isset($param['sms_code'])){
            $sms_code = $param['sms_code'];
            $is_valid = $validate->checkCode($sms_code, '', $param);
        }

        if($is_valid === true){
            echo('true');
        }else{
            echo('false');
        }
    }
}

和上面声明的发送短信方法一样,valid_code 方法只允许 AJAX Post 请求,另外在该方法里我们使用自定义验证规则 checkCode 来验证短信码,从而避免同一功能写多遍。

路由

在配置文件里定义控制方法访问路由规则:

route/route.php

<?php
Route::get('/', 'topic/index')->name('page.root');

// 注册
Route::post('signup/send_code', 'register/send_code')->name('signup.send_code');
Route::post('signup/check_unique', 'register/check_unique')->name('signup.check_unique');
Route::get('signup', 'register/create')->name('page.signup');
Route::post('signup', 'register/save')->name('page.signup.save');

// 验证填写的手机验证码是否正确
Route::post('verify/valid_code', 'verify/valid_code')->name('verify.valid_code');

视图页面

在注册表单页面 手机号码 字段后面添加一个『发送短信』按钮并添加一个验证码字段,添加发送短信代码并修改表单验证规则。

application/index/view/register/create.html

.
.
.
<div class="form-group row">
    <label for="mobile" class="col-md-4 col-form-label text-md-right">手机号码</label>

    <div class="col-md-6">
        <div class="input-group">
            <input id="mobile" type="mobile" class="form-control" name="mobile" required autocomplete="off">

            <div class="input-group-append">
                <button type="button" class="btn btn-primary" id="btn-send-code">
                    发送验证码<span id="notice-seconds" class="hide">(<span id="left-seconds"></span>秒)</span>
                </button>
            </div>
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="sms-code" class="col-md-4 col-form-label text-md-right">短信验证码</label>

    <div class="col-md-6">
        <input id="sms-code" type="text" class="form-control" name="sms_code" required>
    </div>
</div>
.
.
.
{js href="/static/assets/plugins/jquery-validate/jquery.validate.min.js" /}
<script type="text/javascript">
    var default_wait = 60;
    var wait = default_wait;
    function time(){
        var $sendBtn = $("#btn-send-code");
        var $spanNotice = $sendBtn.find("#notice-seconds");
        var $spanSecond = $sendBtn.find("#left-seconds");
        if(wait == 0) {
            $sendBtn.removeClass("btn-secondary").addClass("btn-primary").removeAttr("disabled", "disabled");;
            $spanNotice.addClass("hide");
            wait = default_wait;
        }else{
            if($spanNotice.hasClass("hide")){
                $sendBtn.removeClass("btn-primary").addClass("btn-secondary").attr("disabled", "disabled");
                $spanNotice.removeClass("hide");
            }
            $spanSecond.html(wait);
            wait--;
            setTimeout(function(){
                time()
            }, 1000);
        }
    }

    jQuery(function($){
        // 发送短信验证码
        $("#btn-send-code").click(function(){
            var $btnCurrent = $(this);
            var inputMobile = $("input#mobile");
            if(!inputMobile.length || !inputMobile.val().length){
                alert("请输入您的手机号码");
                return false;
            }
            var mobile = parseInt(inputMobile.val())
            if(mobile < 13000000000 || mobile > 19900000000){
                alert("请输入您的手机号码");
                return false;
            }
            if($btnCurrent.hasClass("btn-primary")){
                $.ajax({
                    url: "{:url('[signup.send_code]')}",
                    type: "POST",
                    dataType: "JSON",
                    data: {"mobile": mobile},
                    success: function(res){
                        if(res["code"]){
                            wait = default_wait;
                            time();
                        }else{
                            alert(res["msg"]);
                        }
                    }, error: function () {
                        alert("数据执行错误!");
                    }
                });
            }
        });

        validAndSubmitForm(
            "form#model-form",
            {
                "name":{
                    required: true,
                    rangelength: [2, 20]
                }, "mobile":{
                    required: true,
                    range: [13000000000, 19900000000]
                    remote: {
                        url: "{:url('[signup.check_unique]')}",
                        type: "post",
                        data: {
                            field: "mobile",
                            id: 0,
                            mobile: function(){
                                return $("input#mobile").val();
                            }
                        }
                    }
                }, "sms_code":{
                    required: true,
                    range: [6, 6]
                    remote: {
                        url: "{:url('[verify.valid_code]')}",
                        type: "post",
                        data: {
                            mobile: function(){
                                return $("input#mobile").val();
                            }
                        }
                    }
                }, "password":{
                    required: true,
                    rangelength: [6, 20]
                }, "password_confirmation":{
                    required: true,
                    rangelength: [6, 20]
                    equalTo: "#password"
                }
            }, {
                "name":{
                    required: "用户名不能为空",
                    rangelength: "用户名长度必须在2-20个字符之间"
                }, "mobile":{
                    required: "手机号码不能为空",
                    min: "手机号码格式不正确",
                    max: "手机号码格式不正确",
                    remote: "当前手机号码已注册"
                }, "sms_code":{
                    required: "短信验证码不正确",
                    rangelength: "短信验证码不正确",
                    remote: "短信验证码不正确"
                }, "password":{
                    required: "登录密码不能为空",
                    rangelength: "登录密码长度必须在6-20之间"
                }, "password_confirmation":{
                    required: "重复密码不能为空",
                    rangelength: "重复密码长度必须在6-20之间",
                    equalTo: "两次输入的密码不一致"
                }
            }
        );
    });
</script>

效果预览

知识点

我们在注册表单里添加了一个 sms_code 字段,但 user 表并没有这个字段,为什么保存时没有报错?

这是因为我们在 User::register 方法里调用了方法 allowField(true),它在 save() 方法执行前过滤掉了数据库不存在的字段。我们给 allowField 方法传的参数是 true 表示所有字段都可以修改,如果我们只允许修改部分字段时可以传递字段名。官方文档

提交代码

下面把代码纳入到版本管理:

$ git add -A
$ git commit '验证用户注册手机是否有效'

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~