Yii2 User cookie 登录原理 2

对于yii2中用户登录,我们可在user组件中设置session的超时时间,另外我们在session中设置session的超时时间,他们的关系是如何呢?
下面是他们的配置:
session组件的配置:

'session' => [
      /**
       * use mongodb for session.
       */
      /*
      'class' => 'yii\mongodb\Session',
      'db' => 'mongodb',
      'sessionCollection' => 'session',
      */
      'class' => 'yii\redis\Session',
      'timeout' => 5,
    ],

User组件的配置:

'user' => [
      'class' 			=> 'fecshop\yii\web\User',
      'identityClass' 	=> 'fecshop\models\mysqldb\Customer',
      # 是否cookie 登录。
      /**
       * @var boolean whether to enable cookie-based login. Defaults to false.
       * Note that this property will be ignored if [[enableSession]] is false.
       */
      'enableAutoLogin' 	=> false,
      'authTimeout' 		=> 86400,
    ],

当session的超时时间为5秒,user的enableAutoLogin设置为false(不使用cookie,而使用session的验证登录方式),超时时间为86400秒,通过结果验证发现,5秒后在刷新页面,发现登录状态失效了,需要重新登录

让把session的超时时间设置为86400,user中的authTimeout设置为5秒,通过结果验证发现,5秒后在刷新页面,发现登录状态失效了,需要重新登录。

结果说明:User 组件,在enableAutoLogin设置为false的情况下(不使用cookie,而使用session的验证登录方式),取的是他们的最小的那个值,原因如下:

public function login(IdentityInterface $identity, $duration = 0)
   {
       if ($this->beforeLogin($identity, false, $duration)) {
           $this->switchIdentity($identity, $duration);
           ....

 

public function switchIdentity($identity, $duration = 0)
    {
        .....
        if ($identity) {
            $session->set($this->idParam, $identity->getId());
            if ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
            if ($this->absoluteAuthTimeout !== null) {
                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
            }
            if ($duration > 0 && $this->enableAutoLogin) {
                $this->sendIdentityCookie($identity, $duration);
            }
        }
    }

identity的id保存一个session,相对当前超时时间保存到一个session,绝对超时时间保存一个session,一共三个session,他们的失效时间为session的配置中的失效时间,因此,如果session组件中配置的时间到期,就无法取出来值,

protected function renewAuthStatus()
    {
        $session = Yii::$app->getSession();
        $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;

        if ($id === null) {
            $identity = null;
        } else {
            /* @var $class IdentityInterface */
            $class = $this->identityClass;
            $identity = $class::findIdentity($id);
        }

上面代码取出来的$id为空,因此session的超时值首先决定一个大范围,

然后user组件在这个范围内设置用户登录状态的超时时间,如果这个值超出session设置的范围,则session的超时值决定登录状态的超时时间。

另外,session的超时时间,默认为相对超时时间,譬如:你设置的为10秒超时,但是你间隔9秒刷新一次,登录状态会一直保持,如果通过配置更改为绝对超时时间,譬如设置绝对绝对超时时间为20秒,那么9秒刷新一次页面,20秒后,登录状态为失效。

if ($this->authTimeout !== null) {
               $session->set($this->authTimeoutParam, time() + $this->authTimeout);
           }
           if ($this->absoluteAuthTimeout !== null) {
               $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
           }
           if ($duration > 0 && $this->enableAutoLogin) {
               $this->sendIdentityCookie($identity, $duration);
           }
if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
            $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
            if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
                $this->logout(false);
            } elseif ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
        }

 

通过上面的代码可以看出,绝对超时时间设置后,如果相对时间不失效,绝对时间失效,那么最终的结果是登录状态失效。

如果enableAutoLogin后,如果登录状态判断失效后,可以通过cookie重新恢复session登录状态,如果登录状态没有失效,cookie的失效时间会更新,也就是当前时间+失效时间为cookie的当前失效时间。

 

最后,需要说明的是,cookie的登录状态的保持的原理为:

if ($this->enableAutoLogin) {
           if ($this->getIsGuest()) {
               $this->loginByCookie();
           } elseif ($this->autoRenewCookie) {
               $this->renewIdentityCookie();
           }
       }

当session失效后,如果cookie开启就会通过cookie获取当前用户信息,最终的登录状态,还是通过session来体现,cookie的作用是在session失效,但是cookie没有实现的情况下起作用,恢复session的登录状态。

 

Yii2 User cookie 登录原理

本文是在session登录畅通无阻的前提下,首先要先搞好session登录,这个在这里不做叙述,好多session登录的文章。本文叙述cookie登录的原理和使用

1.具体实现:

1.1 cookie登录配置config方面,配置好的代码如下:

'components' => [
    'user' => [
       'identityClass' => 'fecshop\models\mysqldb\Customer',
       'enableAutoLogin' => true,
       'authTimeout' => 3600,
       ],
],

enableAutoLogin设置为true,就会使用cookie登录,如果设置了enableAutoLogin,那么下面的超时时间authTimeout就会无效,因为这个参数是session的超时时间,不过,我们可以在登录的时候,超时时间也从这个配置参数读取,譬如下面的函数参数$duration,可以从这里读取,如果这样,就会有效。

$duration = \Yii::$app->user->authtimeout;
\Yii::$app->user->login($this->getCustomer(), $duration);

上面的结论通过代码解释,如下:

public function login(IdentityInterface $identity, $duration = 0)
    {
        if ($this->beforeLogin($identity, false, $duration)) {
            $this->switchIdentity($identity, $duration);
            $id = $identity->getId();
            $ip = Yii::$app->getRequest()->getUserIP();
            if ($this->enableSession) {
                $log = "User '$id' logged in from $ip with duration $duration.";
            } else {
                $log = "User '$id' logged in from $ip. Session not enabled.";
            }
            Yii::info($log, __METHOD__);
            $this->afterLogin($identity, false, $duration);
        }

        return !$this->getIsGuest();
    }

查看 $this->switchIdentity($identity, $duration);

public function switchIdentity($identity, $duration = 0)
    {
        $this->setIdentity($identity);

        if (!$this->enableSession) {
            return;
        }

        /* Ensure any existing identity cookies are removed. */
        if ($this->enableAutoLogin) {
            $this->removeIdentityCookie();
        }

        $session = Yii::$app->getSession();
        if (!YII_ENV_TEST) {
            $session->regenerateID(true);
        }
        $session->remove($this->idParam);
        $session->remove($this->authTimeoutParam);

        if ($identity) {
            $session->set($this->idParam, $identity->getId());
            if ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
            if ($this->absoluteAuthTimeout !== null) {
                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
            }
            if ($duration > 0 && $this->enableAutoLogin) {
                $this->sendIdentityCookie($identity, $duration);
            }
        }
    }

查看:$this->sendIdentityCookie($identity, $duration);

protected function sendIdentityCookie($identity, $duration)
    {
        $cookie = new Cookie($this->identityCookie);
        $cookie->value = json_encode([
            $identity->getId(),
            $identity->getAuthKey(),
            $duration,
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $cookie->expire = time() + $duration;
        Yii::$app->getResponse()->getCookies()->add($cookie);
    }

通过一层层的函数可以看到,cookie的超时时间是从login()函数中读取出来的,

而不是从\yii\web\User的authTimeout变量读取。最后传递给sendIdentityCookie($identity, $duration)方法,进而设置cookie的超时时间。

另外配置中开启了enableAutoLogin,但是在调用login方法的时候没有设置超时时间变量$duration,同样不会设置cookie,进而配置 enableAutoLogin 无效,下面是代码解释:

public function switchIdentity($identity, $duration = 0){    函数中的代码
if ($duration > 0 && $this->enableAutoLogin) {
$this->sendIdentityCookie($identity, $duration);
}

}
可以看到,如果超时时间为0,那么不会执行设置cookie的方法

public function sendIdentityCookie($identity, $duration); 进而不会设置cookie

 

1.2 代码改进:

 

参考代码:

public function getCustomer(){
      if($this->_customer === null){
        $this->_customer = Customer::findByEmail($this->email);
      }
      return $this->_customer;
      
    }
    
    public function login($duration = 86400)
    {
      if ($this->validate()) {
        //return \Yii::$app->user->login($this->getAdminUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
        return \Yii::$app->user->login($this->getCustomer(), $duration);
      } else {
        return false;
      }
    }

在上面的代码,默认cookie的过期时间为86400秒,这样默认就不会cookie超时
如果我我调用:

$model = new CustomerLogin;
$model->email = $data['email'];
$model->password = $data['password'];
$loginStatus = $model->login(0);

由于过期时间填写为0,因此,即使在user组件中开启配置enableAutoLogin=true,
cookie也不会生效。

另外,我如果从\yii\web\User的authTimeout变量读取。来设置cookie的超时时间,也是一个不错的选择,代码如下:

public function login($duration = 0)
    {
    if(!$duration){
      if(Yii::$app->user->authTimeout){
        $duration = Yii::$app->user->authTimeout;
      }
    }
    if ($this->validate()) {
            //return \Yii::$app->user->login($this->getAdminUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
      return \Yii::$app->user->login($this->getCustomer(), $duration);
        } else {
            return false;
        }
    }

 

2. cookie超时时间刷新。

在默认的情况下,如果登录成功账户,每次请求访问Yii::$app->user->identity,都会刷新cookie的超时时间,设置自动更新的变量为:

public $autoRenewCookie = true;

该变量默认为true,所以不需要在配置中设置这个变量,使用默认就好。

cookie超时时间刷新的原理解释,下面是详细代码:

执行Yii::$app->user->identity,对应的是下面的函数

public function getIdentity($autoRenew = true)
   {
       if ($this->_identity === false) {
           if ($this->enableSession && $autoRenew) {
               $this->_identity = null;
               $this->renewAuthStatus();
           } else {
               return null;
           }
       }

       return $this->_identity;
   }

会执行renewAuthStatus()方法

protected function renewAuthStatus()
   {
       $session = Yii::$app->getSession();
       $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;

       if ($id === null) {
           $identity = null;
       } else {
           /* @var $class IdentityInterface */
           $class = $this->identityClass;
           $identity = $class::findIdentity($id);
       }

       $this->setIdentity($identity);

       if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
           $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
           $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
           if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
               $this->logout(false);
           } elseif ($this->authTimeout !== null) {
               $session->set($this->authTimeoutParam, time() + $this->authTimeout);
           }
       }

       if ($this->enableAutoLogin) {
           if ($this->getIsGuest()) {
               $this->loginByCookie();
           } elseif ($this->autoRenewCookie) {
               $this->renewIdentityCookie();
           }
       }
   }

如果enableAutoLogin开启,如果登录就会执行renewIdentityCookie方法。

protected function renewIdentityCookie()
   {
       $name = $this->identityCookie['name'];
       $value = Yii::$app->getRequest()->getCookies()->getValue($name);
       if ($value !== null) {
           $data = json_decode($value, true);
           if (is_array($data) && isset($data[2])) {
               $cookie = new Cookie($this->identityCookie);
               $cookie->value = $value;
               $cookie->expire = time() + (int) $data[2];
               Yii::$app->getResponse()->getCookies()->add($cookie);
           }
       }
   }

该方法会重新设置超时时间,通过上面的几个函数知道了原理,那么问题来了,如果我登录了用户,超时时间设置的为10秒,我开始浏览器文章或者干其他的,每次访问间隔3秒,如果这些页面都没有执下面的行代码:

Yii::$app->user->identity

那么,10秒后,用户登录状态就会被超时,需要重新登录,(有一些间接的方法也会执行上面的代码,譬如Yii::$app->user->isGuest ,就会间接调用执行Yii::$app->user->identity,这种情况下不会超时)。

public function getIsGuest()
   {
       return $this->getIdentity() === null;
   }

 

因此,如果登录状态要持久下去,那么为了保持登录状态,每个页面请求后,都需要执行Yii::$app->user->identity,然后cookie的超时时间就会更新,这样3秒请求一次,登录状态会一直保持下去。

上面是yii2 cookie使用过程中要注意的一些问题。总体来说还是不错,但在实际过程中,还需要结合一下其他,譬如cart 和customer login都使用cookie,超时时间要一致,那么,cart和customer的cookie超时时间要一致,并且,在每次调用Yii::$app->user->identity,都必须执行更新cart的cookie超时时间,因此,需要重写Yii\web\User。

下一个章节介绍实现的具体步骤。

 

在yii2 advanced中使用console

console 是命令行操作,对于frontend和backend是基于web的操作,最终的结果是一样的,都是传递数据给php,下面做一个例子:

1.安装frontend ,然后执行init,可以参看这里:

Yii2 – 安装高级模板 advanced project template

2.新建文件夹/console/controllers/script,然后新建文件TestController.php

3.编辑文件内容:

<?php

namespace console\controllers\script;
use Yii;
use yii\console\Controller;
use fec\helpers\CDate;
use fec\helpers\CConfig;
use appadmin\code\Website\models\WebsiteBaseInfo;
class TestController extends Controller
{
        public $_mongodbdb;


        public function actionGetbegindate(){
                echo '2015-05-20';

        }


        public function actionCreatecollindexer(){
                echo 'create Coll index success';

        }
        public function actionMy($param1,$param2=''){
                echo "param1:".$param1;
                echo "param2:".$param2;
        }


}

4.回到yii advanced的根目录执行:

[root@services datacenter_1000]# ./yii script/test/my  111  2222
param1:111param2:2222
[root@services datacenter_1000]#

解读:

script/test/my 这个和web一样,对应到上面创建的TestController的 actionMy()方法,111为传递的第一个参数,222给传递的第二个参数。

到这里,基本就一个helloworld就写好了,其他的和web没有什么两样。

5.在模块中使用console:(下面是一个在模块TA中使用console的例子)

'modules'=>[

'ta' => [
        'class' => 'appadmin\code\Ta\Module',
        'params'=> [
            'channel_type' => [
                'ppc'    => 'PPC类型',
            ],
        ],
    ],

]

在ta模块的Module中添加代码:

<?php
namespace appadmin\code\Ta;
use Yii;
class Module extends \fec\AdminModule
{
   
    public function init()
    {
    //echo 1;exit;
    # 以下代码必须指定
    # web controller
    if (Yii::$app instanceof \yii\web\Application) {
      $this->controllerNamespace 	= 	__NAMESPACE__ . '\\controllers';
    # console controller
    } elseif (Yii::$app instanceof \yii\console\Application) {
      $this->controllerNamespace 	= 	__NAMESPACE__ . '\\console';
    }
    
    parent::init();  
    
    }
}

通过上面判断是console的application还是web的application,取执行相应的controller

譬如我写的一个导入excel的脚本(在ta模块中):

<?php
namespace appadmin\code\Ta\console\import;
use Yii;
use appadmin\code\Ta\models\WebsiteChannel;
use yii\console\Controller;
use fec\helpers\CExcel;

class ExcelController extends Controller
{
  public function actionChannel(){
    $channel_excel_file = Yii::getAlias('@appta/var/import/channel.xlsx');
    $arr = CExcel::getExcelContent($channel_excel_file);
    //var_dump($arr);
    $i = 0;
    foreach($arr as $one){
      $i++;
      if($i == 1){
        continue;
      }
      $channel 		= trim($one['A']);
      $channel_child 	= trim($one['B']);
      $domain 		= trim($one['C']);
      
    
      $one = WebsiteChannel::find()->where([
        'channel' => $channel,
        'channel_child' => $channel_child,
        'domain' => $domain,
      ])->one();
      if(!$one['id']){
        $WebsiteChannel = new WebsiteChannel;
      }else{
        continue;
      }
      //SEO>SEM>KOL>SNS
      
      
      $WebsiteChannel->channel 		= $channel;
      $WebsiteChannel->channel_child  = $channel_child;
      $WebsiteChannel->domain 		= $domain;
      
      $WebsiteChannel->created_person = 'program_script';
      $WebsiteChannel->save();
      unset($WebsiteChannel);
      
    }
  }
  
  
  
  
}

我新建一个shell文件

appta/shell/import/channel.sh,内容为:

#!/bin/sh
DIR=$(cd `dirname $0`; pwd)
$DIR/../../../yii ta/import/excel/channel

然后执行这个shell就会执行上面的模块Ta里面的console部分。

 

Yii2 制作一个404页面

我是通过模块的方式添加的。

1.添加模块:

'site' => [
    'class' => '\fecshop\app\appfront\modules\Site\Module',
    
  ],

2.新建controller方法:

<?php
namespace fecshop\app\appfront\modules\Site\controllers;
use Yii;
use fec\helpers\CModule;
use fecshop\app\appfront\modules\AppfrontController;
class HelperController extends AppfrontController
{
    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }



}

3.新建view方法

<?php

/* @var $this yii\web\View */
/* @var $name string */
/* @var $message string */
/* @var $exception Exception */

use yii\helpers\Html;

$this->title = $name;
?>
<div class="site-error">

    <h1><?= Html::encode($this->title) ?></h1>

    <div class="alert alert-danger">
        <?= nl2br(Html::encode($message)) ?>
    </div>

    <p>
        The above error occurred while the Web server was processing your request.
    </p>
    <p>
        Please contact us if you think this is a server error. Thank you.
    </p>

</div>

4.设置:(config)

'errorHandler' => [
      'errorAction' => 'site/helper/error',
    ],

这样就添加成功了。

YII2 – application 应用主体

在yii的入口脚本中我们可以看到如下代码:

(new yii\web\Application($config))->run();

从这里开始应用初始化,当然,除了web的应用,还有console的application

yii\console\Application。

那么,我们做一个功能的时候如何区分当前是web还是console呢?

可以通过如下的方法:

if(\Yii::$app instanceof  \yii\web\Application){

}else if(\Yii::$app instanceof  \yii\console\Application){

}

 

通过如上代码可以辨别应用属于哪个应用

yii2 redis 使用 unixSocket 的方式连接 以及配置

yii2 官方除了一个redis连接的扩展,默认的情况下连接redis,并不是最高效,用unixSocket的方式,速度至少提高一半,配置的方式如下:

1.配置如下:

'redis' => [  
            'class' => 'yii\redis\Connection',  
            'hostname' => 'localhost',  
            'port' => 6379,  
            'database' => 0,  
            'password'  => 'rdsFD',  
            'unixSocket' => '/tmp/redis.sock',  
        ],

也就是在unixSocket 项,配置redis.sock的路径。具体的路径,按照redis unixSocket的路径进行设置。

总之:’unixSocket’ => ‘/tmp/redis.sock’,  是配置 unix Socket的链接方式,速度要比TCP的要快一半。

2.配置redis

vim /etc/redis/6379.conf   (在redis的配置文件中进行修改。)

unixsocket /tmp/redis.sock  
unixsocketperm 700

保存,重启redis。

3.这样,redis就配置好了,使用unixSocket速度比较快一些,用默认的方式,对于一般的Yii2网站来说,也没有太多的问题。

yii2 model 输出sql

我们在使用model进行查询的时候,或者其他,我们想要打印出来model赋值后,在mysql数据库中执行的sql,我们可以使用下面的方法打印sql。

$query = Salesorder::find()  
  ->where(['order_id'=>[1,2,3,4]])  
  ->select(['order_id'])  
;  
// get the AR raw sql in YII2  
$commandQuery = clone $query;  
echo $commandQuery->createCommand()->getRawSql();  
exit;

 

yii2 model 规则验证

YII2的model 在设置值的时候,有很多规则的验证,通过规则的验证,来验证前端传递过来的数据,然后在把数据赋值于model。

下面是model的rule:

[['字段名'],required,'requiredValue'=>'必填值','message'=>'提示信息']; #说明:CRequiredValidator 的别名, 确保了特性不为空.

email : 邮箱验证

['email', 'email'];

match : 正则验证

[['字段名'],match,'pattern'=>'正则表达式','message'=>'提示信息'];      
[['字段名'],match,'not'=>ture,'pattern'=>'正则表达式','message'=>'提示信息']; /*正则取反*/ #说明:CRegularExpressionValidator 的别名, 确保了特性匹配一个正则表达式.

url : 网址

['website', 'url', 'defaultScheme' => 'http'];

captcha : 验证码

['verificationCode', 'captcha'];

safe : 安全

['description', 'safe'];

compare : 比较

['age', 'compare', 'compareValue' => 30, 'operator' => '>=']; #说明:compareValue(比较常量值) - operator(比较操作符)  #说明:CCompareValidator 的别名,确保了特性的值等于另一个特性或常量.

default : 默认值

['age', 'default', 'value' => null]; #说明:CDefaultValueValidator 的别名, 为特性指派了一个默认值.

exist : 存在

['username', 'exist'];

file : 文件

['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024]; #说明:CFileValidator 的别名, 确保了特性包含了一个上传文件的名称.

filter : 滤镜

[['username', 'email'], 'filter', 'filter' => 'trim', 'skipOnArray' => true]; #说明:CFilterValidator 的别名, 使用一个filter转换属性.

in : 范围

['level', 'in', 'range' => [1, 2, 3]];

unique : 唯一性

['username', 'unique']

integer : 整数

['age', 'integer'];

number : 数字

['salary', 'number'];

double : 双精度浮点型

['salary', 'double'];

date : 日期

[['from', 'to'], 'date'];

string : 字符串

['username', 'string', 'length' => [4, 24]];

boolean : 是否为一个布尔值

['字段名', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true]; #说明:CBooleanValidator 的别名