yii2项目实战-用户管理之登录与注册功能实现
更新于 2017年08月21日 by 白狼 被浏览了 12691 次
2 2

上一章节我们讲述了如何通过新建用户表来配置yii2的用户认证类,但是课后有小伙伴发来问卷,为啥在创建user_backend数据表的时候销毁了 password_reset_token 字段呢?其实这个字段对后台管理基本没啥子用,你要是非要,也可以,这里只对该字段以及用途做一个说明,课后需要的可以自行实现。

该字段具有唯一性,其用途在于用户找回密码。且该字段具有时效性,过期时间参考 common\config\params.php 文件的 user.passwordResetTokenExpire 项配置,过期时间默认是1小时。注意哦,默认的找回密码是基于邮件且发送的链接内容包含该字段,用于点击链接跳转后可根据该字段获取到具体用户并实现用户密码的修改。因为看起来还是蛮繁琐的,如果你没听懂,可以在小站注册一个帐号,通过邮件找回密码测试一番。

因此,考虑到后台,无需这么繁琐,后面我们增加一个密码重置的功能,直接让管理员重置密码即可。

补充:今天我们第一次谈及params.php (params-local.php的含义参考我们前文对main.php和main-local.php的总结),这个文件也是配置文件,主要用于配置一些固定键值对。比如我们配置一个全局的 adminEmail ,我们就可以在 common\config\params.php 、 common\config\params-local.php 、 app\config\params.php 、 app\config\params-local.php 这四个文件内的任意一个文件内添加一个kv键值对

<?php
return [
    'adminEmail' => '422744746@qq.com',
];

这四个文件的优先级以及冲突均可参考前文对main.php和main-local.php的介绍

对于这种全局配置的引用很简单,像下面这样

echo Yii::$app->params['adminEmail'];

言归正传,我们接着上一章节,来说一说创建新用户和登录机制的实现。

其实也是so easy的,跟着步骤走,操作起来简单易学,一学就会,简直不能再6!

提醒:本文需要耐心的阅读,全程跟着做下来,你至少应该保证会用yii2了。我推荐你这样学:先跟着我走一遍,自己再敲一遍,如果不能达到此目的,请多花点时间来第三遍。

新用户的创建

打开 index.php?r=user-backend 页面,我们发现有一个创建的按钮,当然,如果真的就这么创建了,创建的用户肯定是没有办法登录的,至少密码我们这里没有加密吧,对不对?

接下来我们就来实现添加一个新用户的操作,然后再一并实现登陆的操作。

①、我们知道这一段路由 index.php?r=user-backend 对应的控制器是 backend\controllers\UserBackendController.php,相应的操作是index,index操作对应的模板文件是 backend\views\user-backend\index.php,我们打开视图文件 index.php,先将【创建】的按钮改为【添加新用户】,并修改其对应的路由

<?= Html::a('添加新用户', ['signup'], ['class' => 'btn btn-success']) ?>

补充:这里的 Html 类,实际上是我们在index.php文件顶部 use过来的类,即 yii\helpers\Html,这个是官方提供的工具类,可以为我们提供大有利的帮助。

有同学说我这里还不如直接写一个a链接呢,Html::a多麻烦!

注意哦,千万不要这么想!这是因为如果你手动写一个a链接,这个链接就是“死”的,固定的,而我们通过 Html::a 生成的a链接是动态的。在yii2内,有一个核心组件UrlManager,我们可以通过这个urlManager组件配置很多路由规则,如果你有足够的好奇心,可以点击这里看看后续哦。

剩下没去看的,这里就听我的,乖乖的用 Html::a 生成一个动态的链接吧。

我们刚刚创建了一个signup操作,下面就让我们去实现这个操作吧。

②、打开对应的控制器backend\controllers\UserBackendController.php,我们添加一个signup操作。

听说你有点头懵,不知道要写啥?

来,我先给你分析一下:

1)首先我们是不是需要一个可以添加新用户的表单页面?
2)表单填写好了呢,是不是需要接收表单数据,验证表单数据,过滤表单数据,最后再将表单数据入库?

得嘞,这就是我们要实现的小目标。

/**
 *  create new user
 */
public function actionSignup ()
{
    // 实例化一个表单模型,这个表单模型我们还没有创建,等一下后面再创建
    $model = new \backend\models\SignupForm();

    // 下面这一段if是我们刚刚分析的第二个小问题的实现,下面让我具体的给你描述一下这几个方法的含义吧
    // $model->load() 方法,实质是把post过来的数据赋值给model的属性
    // $model->signup() 方法, 是我们要实现的具体的添加用户操作
    if ($model->load(Yii::$app->request->post()) && $model->signup()) {
        // 添加完用户之后,我们跳回到index操作即列表页
        return $this->redirect(['index']);
    }

    // 下面这一段是我们刚刚分析的第一个小问题的实现
    // 渲染添加新用户的表单
    return $this->render('signup', [
        'model' => $model,
    ]);
}

上面这一段代码即实现了我们刚刚描述的两个小问题,具体含义都写在了备注里面,希望你看备注跟阅读文章一样细心。

③、从第②步我们知道,接下来我们需要创建signup视图文件了。我们在 backend\views\user-backend 目录下创建一个文件名叫 signup.php 的文件,其代码如下

<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \backend\models\SignupForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;

$this->title = '添加新用户';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-signup">
    <div class="row">
        <div class="col-lg-5">
            <?php $form = ActiveForm::begin(['id' => 'form-signup']); ?>
                <?= $form->field($model, 'username')->label('登陆名')->textInput(['autofocus' => true]) ?>
                <?= $form->field($model, 'email')->label('邮箱') ?>
                <?= $form->field($model, 'password')->label('密码')->passwordInput() ?>
                <div class="form-group">
                    <?= Html::submitButton('添加', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
                </div>
            <?php ActiveForm::end(); ?>
        </div>
    </div>
</div>

想一下我们平时怎么创建表单的?

写一段form => 写一堆input => 写一段js校验表单数据...

yii2,内置支持jquery和bootstrap,创建表单的操作我们这里完全由 yii\bootstrap\ActiveForm 类去实现。用ActiveForm实现的好处等下你就明白了,我们先不说,你先这样写。

④、表单页面是渲染好了,但是我们现在还看不到效果。因为我们在渲染表单时所用到的表单模型 backend\models\SignupForm 还没有创建。表单模型是个什么鬼,且听我慢慢道来。

假设表单模型已经存在了,刚好我们也能在浏览器通过地址看到实际渲染的表单,用户填写完表单我们就可以直接让数据入库了吗?我们是不是还要再写一段js验证下表单内的数据是否有效?是否合法呢?当然这可能很麻烦,input失去焦点事件啦,表单提交事件啦等等很繁琐。

在yii2中,对表单的集成可谓一气呵成。像我们刚刚第③步,渲染表单只是一部分基础的功能,除此之外,表单数据的校验yii2也给我们提供了支持。

来,让我们看看以后经常打交道的表单模型的rules吧。

rules,顾名思义,就是规则的意思。这里指的是需要校验哪些字段,需要以哪种方式去校验,比如是手机号还是email,是数字还是字符串,字符串长度多少等等等等,我们一行js代码都不需要写,直接写rules就好啦。

同时我们注意到在signup操作中,我们调用了表单模型的load方法和signup方法,其中load方法是yii2内置的方法,signup方法就是我们实现注册逻辑的操作啦。

说了这么多,下面我们就来看看表单模型 backend\models\SignupForm 的实现吧。在backend\models 目录下创建SignupForm.php,代码如下

<?php
namespace backend\models;

use yii\base\Model;
use backend\models\UserBackend;

/**
 * Signup form
 */
class SignupForm extends Model
{
    public $username;
    public $email;
    public $password;
    public $created_at;
    public $updated_at;

    /**
     * @inheritdoc
     * 对数据的校验规则
     */
    public function rules()
    {
        return [
            // 对username的值进行两边去空格过滤
            ['username', 'filter', 'filter' => 'trim'],
            // required表示必须的,也就是说表单提交过来的值必须要有, message 是username不满足required规则时给的提示消息
            ['username', 'required', 'message' => '用户名不可以为空'],
            // unique表示唯一性,targetClass表示的数据模型 这里就是说UserBackend模型对应的数据表字段username必须唯一
            ['username', 'unique', 'targetClass' => '\backend\models\UserBackend', 'message' => '用户名已存在.'],
            // string 字符串,这里我们限定的意思就是username至少包含2个字符,最多255个字符
            ['username', 'string', 'min' => 2, 'max' => 255],
            // 下面的规则基本上都同上,不解释了
            ['email', 'filter', 'filter' => 'trim'],
            ['email', 'required', 'message' => '邮箱不可以为空'],
            ['email', 'email'],
            ['email', 'string', 'max' => 255],
            ['email', 'unique', 'targetClass' => '\backend\models\UserBackend', 'message' => 'email已经被设置了.'],
            ['password', 'required', 'message' => '密码不可以为空'],
            ['password', 'string', 'min' => 6, 'tooShort' => '密码至少填写6位'],   
            // default 默认在没有数据的时候才会进行赋值
            [['created_at', 'updated_at'], 'default', 'value' => date('Y-m-d H:i:s')],
        ];
    }

    /**
     * Signs user up.
     *
     * @return true|false 添加成功或者添加失败
     */
    public function signup()
    {
        // 调用validate方法对表单数据进行验证,验证规则参考上面的rules方法,如果不调用validate方法,那上面写的rules就完全是废的啦
        if (!$this->validate()) {
            return null;
        }
        // 实现数据入库操作
        $user = new UserBackend();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->created_at = $this->created_at;   
        $user->updated_at = $this->updated_at;
        // 设置密码,密码肯定要加密,暂时我们还没有实现,继续阅读下去,我们在下面有实现
        $user->setPassword($this->password);
        // 生成 "remember me" 认证key
        $user->generateAuthKey();
        // save(false)的意思是:不调用UserBackend的rules再做校验并实现数据入库操作
        // 这里这个false如果不加,save底层会调用UserBackend的rules方法再对数据进行一次校验,这是没有必要的。
    // 因为我们上面已经调用Signup的rules校验过了,这里就没必要再用UserBackend的rules校验了
        return $user->save(false);
    }
}

上面我们在rules方法中介绍了很多种规则,有些人一看又又又头懵啦,还不如我直接去写js呢。

这种心态是万万要不得的,不然你还学习yii2干啥呢?直接去写原声的php代码好了嘛。上面的代码只希望你一行一行的敲,在敲的同时自然会琢磨出一些规律的。

⑤、我们在 SignupForm 的 signup 方法中发现,这里还需要实现 UserBackend 的 setPassword 和 generateAuthKey 方法,这两个方法都是干嘛的呢?且看我们下面在function上面的备注信息

打开 backend\models\UserBackend.php 文件新增下面两个方法

/**
 * 为model的password_hash字段生成密码的hash值
 *
 * @param string $password
 */
public function setPassword($password)
{
    $this->password_hash = Yii::$app->security->generatePasswordHash($password);
}


/**
 * 生成 "remember me" 认证key
 */
public function generateAuthKey()
{
    $this->auth_key = Yii::$app->security->generateRandomString();
}

ok,渲染表单页面我们基本已准备充分啦,现在我们访问 index.php?r=user-backend/signup 看看添加新用户的页面吧

0bd365a984-yii2-user.png

接着我们测试下添加一个新用户test1,发现也是可以的。

f6a0462bab-yii2-user2.png

功能是实现了,我们暂停一下,回过头来缕缕整个代码实现的逻辑

ce8fb4e327-blogadduser.png

登录的实现

从上面的操作中,我们看到,已经可以成功添加新用户了,接下来就是关键时刻,我们要让新用户可以登录系统!万一登录不上,那就是我们创建的用户存在问题了。

开始之前,参考刚刚实现的添加新用户,总结一下实现登录功能至少也需要下面几个步骤

①、创建一个登录的表单模型

②、利用 yii\bootstrap\ActiveForm 类创建一个表单页面

③、完善表单模型的rules方法,接收登录表单数据并校验过滤,然后不是入库了,是校验密码,正确后,登录的信息存session

可喜的是,上面三个小步骤advanced版本都帮我们实现了,实现了,实现了!

下面我们就来简单分析一下登录的流程

看第①步,我们打开SiteController文件,找到actionLogin操作

public function actionLogin()
{
    // 判断用户是访客还是认证用户 
    // isGuest为真表示访客,isGuest非真表示认证用户,认证过的用户表示已经登录了,这里跳转到主页面
    if (!Yii::$app->user->isGuest) {
        return $this->goHome();
    }
    // 实例化登录模型 common\models\LoginForm
    $model = new LoginForm();
    // 接收表单数据并调用LoginForm的login方法
    if ($model->load(Yii::$app->request->post()) && $model->login()) {
        return $this->goBack();
    } 
    // 非post直接渲染登录表单
    else {
        return $this->render('login', [
            'model' => $model,
        ]);
    }
}

②、看LoginForm的login等方法的具体实现

use backend\models\UserBackend as User;
/**
 * @inheritdoc
 * 对表单数据进行验证的rule
 */
public function rules()
{
    return [
        // username和password必须
        [['username', 'password'], 'required'],
        // rememberMe是一个boolean值
        ['rememberMe', 'boolean'],
        // 这里需要注意的是 validatePassword 是自定义的验证方法!!!只需要在当前模型内增加对应的认证方法即可
        ['password', 'validatePassword'],
    ];
}
/**
 * 自定义的密码认证方法
 * This method serves as the inline validation for password.
 *
 * @param string $attribute the attribute currently being validated
 * @param array $params the additional name-value pairs given in the rule
 */
public function validatePassword($attribute, $params)
{
    // hasErrors方法,用于获取rule失败的数据
    if (!$this->hasErrors()) {
        // 调用当前模型的getUser方法获取用户
        $user = $this->getUser();
        // 获取到用户信息,然后校验用户的密码对不对,校验密码调用的是 backend\models\UserBackend 的validatePassword方法,
        // 这个我们下面会在UserBackend方法里增加
        if (!$user || !$user->validatePassword($this->password)) {
            // 验证失败,调用addError方法给用户提醒信息
            $this->addError($attribute, 'Incorrect username or password.');
        }
    }
}
/**
 * Logs in a user using the provided username and password.
 *
 * @return boolean whether the user is logged in successfully
 */
public function login()
{
    // 调用validate方法 进行rule的校验,其中包括用户是否存在和密码是否正确的校验
    if ($this->validate()) {
        // 校验成功后,session保存用户信息
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
    } else {
        return false;
    }
}
/**
 * 根据用户名获取用户的认证信息
 *
 * @return User|null
 */
protected function getUser()
{
    if ($this->_user === null) {
        // 根据用户名 调用认证类 backend\models\UserBackend 的 findByUsername 获取用户认证信息
        // 这个我们下面会在UserBackend增加一个findByUsername方法对其实现
        $this->_user = User::findByUsername($this->username);
    }
    return $this->_user;
}

③、从上面的②操作中,我们需要在用户认证类 backend\models\UserBackend 中增加 findByUsername方法和validatePassword方法

/**
 * 根据user_backend表的username获取用户
 *
 * @param string $username
 * @return static|null
 */
public static function findByUsername($username)
{
    return static::findOne(['username' => $username]);
}
/**
 * 验证密码的准确性
 *
 * @param string $password password to validate
 * @return boolean if password provided is valid for current user
 */
public function validatePassword($password)
{
    return Yii::$app->security->validatePassword($password, $this->password_hash);
}

最后,我们基本上实现了用户认证类所有的操作,接下来就是见证奇迹的时刻。

我们访问 index.php?r=site/login , 大写的login页面呈现在我们面前。让我们快填写一开始添加的新用户的用户名和密码吧。

7d1d8fbfa8-yii2-user3.png

然后,没报任何错误!!!然后我们就可以直接访问管理平台的首页面site/index了!!!

最后,我们再回过头来总结一下登录的逻辑实现

7ce606deb2-bloguserlogin.png

至此,我们关于yii2登录注册模块分别讲述完了!哪有大部分同学说的那么难,这不我们也换数据表了,也不难实现的嘛!

下一章节,我们会讲述一下简单的授权访问机制,敬请期待吧!