45. 重置密码
简介
在本节里,我们将实现当用户忘记登录密码后通过注册手机重设登录密码功能。
需求分解
- 重置密码时用户需要填写注册手机号码、短信验证码、新登录密码和再次输入登录密码;
- 当用户填写的手机号码未注册时给出错误提示;
- 当用户填写手机号码已注册时点击『发送短信』按钮给用户发送手机短信验证码;
- 同一手机号码每 60 秒内只能发送一次短信验证码;
- 用户收到短信验证码后必须在 60 内填写完重置表单,否则验证码失效;
- 登录密码长度必须在 6-20 个字符之间,并且两次输入的登录密码相同。
验证器
仔细阅读功能需求,我们发现置密码功能与用户注册表单的短信验证码和登录密码验证相似,所以我们打算通过在 app\common\validate\User 里使用 场景 实现本功能的开发。
首先,我们按照场景规则,我们在验证器里定义下面两个场景:
- form_register 用于验证用户注册表单;
- reset_password 用于验证重置密码表单。
application/common/validate/User.php
<?php
namespace app\common\validate;
use think\Validate;
use think\facade\Cache;
class User extends Validate
{
.
.
.
protected $scene = [
'form_register' => ['name', 'mobile', 'password', 'password_confirmation', 'sms_code'],
'reset_password' => ['sms_code', 'password', 'password_confirmation'],
];
}
如上面代码所示,场景 form_register 还是验证之前注册表单提交的所有字段,而 reset_password 只验证手机短信码和两次输入的登录密码。
数据模型
接下来,我们在 User::register 方法里使用 form_register 场景来验证表单数据,并在 User 模型里添加重置密码方法:
application/common/model/User.php
<?php
.
.
.
class User extends Model
{
public static function register($data)
{
$validate = new Validate;
if(!$validate->scene('form_register')->batch(true)->check($data)){
$e = new ValidateException('注册数据验证失败');
$e->setData($validate->getError());
throw $e;
}
.
.
.
}
.
.
.
/**
* 重置密码
* @Author zhanghong(Laifuzi)
* @DateTime 2019-06-17
* @param array $data [description]
* @return boolean [description]
*/
public static function resetPassword($data)
{
if(!isset($data['mobile'])){
$e = new ValidateException('重置密码验证失败');
$e->setData(['mobile' => '注册手机号码不能为空']);
throw $e;
}
$user = self::where('mobile', $data['mobile'])->find();
if(empty($user)){
$e = new ValidateException('重置密码验证失败');
$e->setData(['mobile' => '注册手机号码不在存']);
throw $e;
}
$validate = new Validate;
if(!$validate->batch(true)->scene('reset_password')->check($data)){
$e = new ValidateException('重置密码验证失败');
$e->setData($validate->getError());
throw $e;
}
$user->password = $data['password'];
$is_save = $user->save();
if(!$is_save){
throw new \Exception('重置密码失败');
}
return true;
}
}
在模型里实现重置密码功能时,我们之所以没有把手机号码(mobile)也添加到验证器场景里是因为注册时要求填写的手机号码必须没有注册过,而重置密码时填写的手机号码必须格式正确而且还是已注册的,这两个场景的验证规则不一样。
控制器
接下来,创建重置密码控制器:
$ php think make:controller index/Reset
新建控制器代码如下:
application/index/controller/Reset.php
<?php
namespace app\index\controller;
use think\Request;
use think\facade\Session;
use app\common\model\Sms;
use app\common\model\User;
use app\common\exception\ValidateException;
class Reset extends Base
{
public function create()
{
return $this->fetch('create');
}
public function save(Request $request)
{
if(!$request->isAjax()){
$this->redirect('[page.reset]');
}else if(!$request->isPost()){
$this->error('访问页面不存在');
}
$param = $request->post();
try{
User::resetPassword($param);
}catch (ValidateException $e){
$this->error('验证失败', '', ['errors' => $e->getData()]);
}catch (\Exception $e){
$this->error($e->getMessage());
}
$message = '重置密码成功';
Session::set('success', $message);
$this->success($message, url('[page.login]'));
}
public function send_code(Request $request)
{
if(!$request->isAjax()){
$this->redirect('[page.reset]');
}else if(!$request->isPost()){
$this->error('访问页面不存在');
}
$mobile = $request->post('mobile');
if(empty($mobile)){
$this->error('注册手机号码不能为空');
}else if(!User::where('mobile', $mobile)->count()){
$this->error('注册手机号码不在存');
}
try {
$sms = new Sms();
$sms->sendCode($mobile);
} catch (\Exception $e) {
$this->error($e->getMessage());
}
$this->success('验证码发送成功');
}
public function mobile_present(Request $request)
{
if(!$request->isAjax()){
$this->redirect('[page.reset]');
}else if(!$request->isPost()){
$this->error('访问页面不存在');
}
$mobile = $request->post('mobile');
if(empty($mobile)){
echo('false');
}else if(User::where('mobile', $mobile)->count()){
echo('true');
}else{
echo('false');
}
}
}
路由
在配置文件里定义控制方法访问路由规则:
route/route.php
<?php
.
.
.
// 重置密码
// 发送手机验证码
Route::post('reset/send_code', 'reset/send_code')->name('reset.send_code');
// 验证手机是否已注册
Route::post('reset/mobile_present', 'reset/mobile_present')->name('reset.mobile_present');
// 重置密码表单和保存方法
Route::get('reset', 'reset/create')->name('page.reset');
Route::post('reset', 'reset/save')->name('reset.save');
视图页面
- 创建重置密码视图表单页面:
application/index/view/reset/create.html
{extend name="layout/main" /}
{block name="content"}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
重置密码
</div>
<div class="card-body">
<form id="model-form" class="needs-validation" novalidate method="POST" action="{:url('[reset.save]')}">
<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="col-md-6 col-form-label text-md-right" name="mobile" required autocomplete="off">
<span class="input-group-btn">
<button type="button" class="btn btn-primary" id="btn-send-code">
发送验证码<span id="notice-seconds" class="hide">(<span id="left-seconds"></span>秒)</span>
</button>
</span>
</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>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">登录密码</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">重复密码</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
提交
</button>
<a class="btn btn-link" href="{:url('[page.login]')}">
账号登录
</a>
<a class="btn btn-link" href="{:url('[page.signup]')}">
立即注册
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{/block}
{block name="scripts"}
{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('[reset.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",
{
"mobile":{
required: true,
range: [13000000000, 19900000000],
remote: {
url: "{:url('[reset.mobile_present]')}",
type: "post"
}
}, "sms_code":{
required: true,
rangelength: [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"
}
}, {
"mobile":{
required: "手机号码不能为空",
range: "手机号码格式不正确",
remote: "注册手机号码不在存"
}, "sms_code":{
required: "短信验证码不正确",
range: "短信验证码不正确",
remote: "短信验证码不正确"
}, "password":{
required: "登录密码不能为空",
rangelength: "登录密码长度必须在6-20之间"
}, "password_confirmation":{
required: "重复密码不能为空",
rangelength: "重复密码长度必须在6-20之间",
equalTo: "两次输入的密码不一致"
}
}
);
});
</script>
{/block}
- 修改用户注册页面的「重置密码」链接地址:
application/index/view/register/create.html
.
.
.
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
注册 <i class="glyphicon glyphicon-arrow-right"></i>
</button>
<a class="btn btn-link" href="{:url('[page.login]')}">
账号登录
</a>
<a class="btn btn-link" href="{:url('[page.reset]')}">
忘记密码
</a>
</div>
</div>
.
.
.
- 修改登录页面的「重置密码」链接地址:
application/index/view/login/create.html
.
.
.
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
登录
</button>
<a class="btn btn-link" href="{:url('[page.signin]')}">
立即注册
</a>
<a class="btn btn-link" href="{:url('[page.reset]')}">
忘记密码
</a>
</div>
</div>
.
.
.
效果展示
提交代码
下面把代码纳入到版本管理。
$ git add -A
$ git commit '重置密码'
推荐文章: