一、cookie 与 session
我们也正是利用了cookie与session的唯一性,来实现了用户认证。基本的验证流程如下:
二、静态方法
在前面的章节中,我们接触到了很多类似于这样的方法类名::方法名()。比如:我们使用Db::name()
来获取某个数据表模型,使用Resquest::instance()
来获取请求模型使用了::get()
来获取单条数据模型,使用::select()
来获取多条数据模型,使用::destroy
来销毁数据。
ThinkPHP5大量的使用了这种可以直接使用::调用的方法,它们有一个很响亮的名字:静态方法。静态方法的引用,大幅提升了程序的运行效率,降低了资源的占用。在实际项目开发中,由于静态方法的优越性,我们还会大量使用。虽然本教程的前面也简单的对静态方法进行过讲解,但距离『理解』这个程度还不够。
争取通过本章的学习,能够让静态方法在我们心中留下更深的印象。
静态方法
为什么要有静态方法?
- 可以降低内存的占用。
- 提高程序的执行效率。
什么时候用静态方法?
- 当这个方法与具体的对象无关时。
其它略详见:https://www.kancloud.cn/diehl/thinkphp/930241
三、规划URL跳转
本节,我们对登录成功与失败进行URL的跳转规划。
登录 --> 提示登录成功 --> 跳转到 首页
登录 --> 提示登录失败 --> 跳转到 登录页
在软件工程中,我们往往会专门针对用户看到的各个界面做一个规划,这个图我们叫做“用例图”。在以后我们的实际项目中,我们将更多的在软件工程的规范下进行开发。
四、登录流程
1)功能分析
a) 和“添加”、“编辑”一样,“登录”也是两个action在共同起作用:
- 第一个action提供与用户交互的界面,
- 第二个action处理用户传入的数据。
b) 新建一个控制器LoginController,用于实现用户登录与注销的相关功能。
在这个类中,我们新建两个action:
- index:提供 【显示登录表单】功能
- login:提供【处理用户提交的登录数据】功能。
2)写基本功能
1.新建app\controller\LoginController.php
<?php
namespace app\index\controller;
class LoginController{
//用于展示登录页登录表单
public function index(){
return 'index';
}
// 用于处理用户提交的登录数据
public function login(){
return 'login';
}
}
测试基础代码是否正常:http://tp5/index.php/index/login/index
显示 index
http://tp5/index.php/index/login/login
显示 login
2.新建application\index\view\login\index.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<!--action连接login函数-->
<form action="{:url('login')}" method="post">
<label for="username">username:</label><input type="text" name="username" id="username" />
<label for="password">password:</label><input type="password" name="password" id="password" />
<button type="submit">submit</button>
</form>
</body>
</html>
3)确定流程
测试完action与对应的URL没有问题后,我们按流程来加入注释:
用户登录表单index-action::
// 显示登录表单
处理用户提交的登录数据login-action:
// 验证用户名是否存在
// 验证密码是否正确
// 用户名密码正确,将teacherId 存session
// 用户名密码错误,跳转到登录界面
4)index-action
<?php
namespace app\index\controller;
use think\Controller; /【1】必须引入controller类,否则报错找不到类
/【2】继承自tp的controller类
class LoginController extends Controller
{
//用于展示登录页登录表单
public function index(){
/【3】显示v层登录表单
return $this->fetch();
}
// 用于处理用户提交的登录数据
public function login(){
return 'login';
}
}
效果:http://tp5/index.php/index/login/index
5)login-action
5.1插入password字段:类型varchar 长40 不为空
5.2增加测试密码
直接双击增加
input()助手函数用法:
https://blog.csdn.net/xinyflove/article/details/89486213
5.3测试代码是否有问题
// 用于处理用户提交的登录数据
public function login(){
var_dump(input()); //同 input('post.')
// return 'login';
}
效果 http://tp5/index.php/index/login/index
提交后:
array(2) {
["username"]=>
string(4) "lisi"
["password"]=>
string(3) "123"
}
5.4C层逻辑
// 处理用户提交的登录数据
public function login()
{
// 1.验证用户名是否存在
// 2.验证密码是否正确
// 3.用户名密码正确,将teacherId存session。
// 4.用户名密码错误,跳转到登录界面。
}
5.5 实现
<?php
namespace app\index\controller;
use think\Controller; //必须引入controller类
use think\Request; //获取表单数据
use app\common\model\Teacher; //使用teacher模型
class LoginController extends Controller
{
//用于展示登录页登录表单
public function index(){
//显示v层登录表单
return $this->fetch();
}
// 用于处理用户提交的登录数据
public function login(){
//var_dump(input());
// return 'login';
// 接收post数据
$postData=Request::instance()->post();
// 验证用户名是否存在
$map=['username'=>$postData['username']];
$Teacher=Teacher::get($map);
// 验证密码是否正确
if(!is_null($Teacher)){
// 用户名密码错误,跳转到登录界面
if($Teacher->getData('password')!==$postData['password']){
return $this->error('密码错误',url('index'));
}else{
// 用户名密码正确,将teacherId存session
session('teacher_id',$Teacher->getData('id'));
return $this->success('登录成功',url('teacher/index'));
}
}else{
return $this->error('用户名不存在',url('index'));
}
}
}
知识点
由于$this->error()
本身就是在抛出异常,所以以后我们在C层
中的代码,将所有需要抛出异常的代码都统一写为$this->error()
。异常抛出后,交给ThinkPHP
为我们进行处理。
这也意味着:
我们以后在C层将不再有类似throw new \Exception
这样直接抛出异常的代码。
效果:http://tp5/index.php/index/login/index.html
输入正确密码:登录成功,跳转到teacher表格列表页
输入错误密码:密码错误,回登录页
输入错误名:用户名不存在,回登录页
5.6简化重构
//login函数简化重构
public function login(){
//接收登录传来数据
$postData=Request::instance()->post();
//查询对应的用户
$map=['username'=>$postData['password']]; //username对应的密码数组
$Teacher=Teacher::get($map); //查询对应用户的密码返回1个对象
//判断用户对象是否存在,且密码等于表单传过来的数据
if (!is_null($Teacher) && $Teacher->getData('password')===$postData['password'] ) {
//把id保存到session中
session('teacher_id',$Teacher->getData('id'));
//如果密码正确返回正确信息且登录到teacher/index
return $this->success('登录成功',url('teacher/index'));
}else{
//如果密码错误则返回错误信息
return $this->error('用户名或密码不正确',url('index'));
}
}
说明
就!is_null($Teacher) && ($Teacher->getData('password') === $postData['password'])
这行代码而言,我们是不需要担心,如果$Teacher
为null的话,执行到$Teacher->getData()
会报不能在一个bool类型的变量上调用method的错误的。因为如果$Teacher
的值是null,那么当执行到!is_null($Teacher)
, 系统已经判断条件不被满足,当然也就不会执行**&&**后面的语句了。
再举个简单的例子:
$a = 1;
$b = 2;
if ($a !== 1 && $b++)
{
echo 'hello';
}
echo $b; // 值没有发生变化? 没有变化,还是2。
五、把登录逻辑改到M层
C层是个指挥官,它并不负责代码的具体实现。它的作用就是:指挥!然后,查看指挥结果,进而做出下一步的指挥。
因此要把登录逻辑改到M层去
1.c层登录代码重构
application\index\controller\loginController.php
//把登录逻辑放到M层
public function login(){
$postData=Request::instance()->post();
if(Teacher::login($postData['username'],$postData['password'])){
return $this->success('登录成功',url('teacher/index'));
}else{
return $this->error('登录失败',url('index'));
}
}
2.M层登录及判断逻辑编写
application\common\model\teacher.php
<?php
// 简单的原理重复记: namespace说明了该文件位于application\common\model 文件夹
namespace app\common\model;
use think\Model; // 导入think\Model类
/**
* Teacher 教师表
*/
/*我的类名叫做Teacher,对应的文件名为Teacher.php.
该类继承了Model类,Model我们在文件头中,提前使用use进行了导入*/
class Teacher extends Model
{
/**
* @param string $username 用户名
* @param string $password 密码
* @return bool 成功true,失败false
*/
static public function login($username,$password){
// 查询用户名将其对象赋值给$Teacher
$map=['username'=>$username];
$Teacher=self::get($map);
//如果上步返回对象不为空,证明用户存在继续:
if(!is_null($Teacher)){
//查询对应用户的密码是否正确,调用checkpassword()
if($Teacher->checkPassword($password)){
//正确则把用户id存入session
session('teacherId',$Teacher->getData('id'));
//返回true,证明登录成功
return true;
}
}
return false; //其它情况登录失败
}
/**
* 验证密码是否正确
* @param string $password 密码
* @return bool
*/
public function checkPassword($password)
{ //查询对应用户密码,是否等于传过来的密码
if($this->getData('password')===$password){
return true;
}else{
return false;
}
}
}
六、密码加密及验证
1.加密及验证
application\common\model\teacher.php
/**
* 验证密码是否正确
* @param string $password 密码
* @return bool
*/
public function checkPassword($password)
{
//【1】数据库密码 === 前端提交密码,加密后
if($this->getData('password') === $this::encryptPassword($password)){
return true;
}else{
return false;
}
}
/**
* 【2】加密算法
* @param string 待加密字串
* @return string 加密后字串
*/
static public function encryptPassword($password)
{
// 实际的过程中,我还还可以借助其它字符串算法,来实现不同的加密
//md5加密后+pasa 再sha1加密
return sha1(md5($password).'pas');
}
2.生成默认密码加密码
application\index\controller\loginController.php
//生成测试密码123 的加密码,并加入数据库
public function test(){
echo Teacher::encryptPassword('123');
// $map=['username'=>'zhangsan']; //生成关联数组
// $Teacher=Teacher::get($map); //获取对应zhangsan对象
// return $Teacher->getData('password'); //返回zhangsan密码
}
测试登录:http://tp5/index.php/index/login/index.html
zhangsan 123 提示登录成功
七、订制异常
1.尝试把数组传入加密函数
<?php
namespace app\index\controller;
use think\Controller; //必须引入controller类
use think\Request;
use app\common\model\Teacher;
class LoginController extends Controller
{
...
//生成测试密码
public function test(){
echo Teacher::encryptPassword(['123']);
// $map=['username'=>'zhangsan']; //生成关联数组
// $Teacher=Teacher::get($map); //获取对应zhangsan对象
// return $Teacher->getData('password'); //返回zhangsan密码
}
报错:http://tp5/index.php/index/login/test
2.订制异常
<?php
// 简单的原理重复记: namespace说明了该文件位于application\common\model 文件夹
namespace app\common\model;
use think\Model; // 导入think\Model类
class Teacher extends Model
{
...
/**
* 加密算法
* @param string 待加密字串
* @return string 加密后字串
*/
static public function encryptPassword($password)
{
//【1】如果传入的不是字符串,则抛出异常
if(!is_string($password))
{
throw new \RuntimeException("传入的不是字符串,请检查后重试", 2);
}
// 实际的过程中,我还还可以借助其它字符串算法,来实现不同的加密
//md5加密后+pasa 再sha1加密
return sha1(md5($password).'pas');
}
订制的异常效果
手动的抛出了一个RuntimeException异常来替代Exception异常,这样做的目的是细化异常类型。后面还抛出了一个状态码,如果在加上一个我们后期应该完善的错误码说明表,那么可以快速的定位错误类型。比如,微信的官方文档中,就专门有对返回码说明的章节:
https://mp.weixin.qq.com/wiki/17/fa4e1434e57290788bde25603fa2fcbd.html
在实际的开发过程中,我们需要定制系统的返回码,完善错误码说明表。按不同的异常类型进行异常的抛出。每个方法中,都在使用异常来处理一些非正常的输入,来保证程序的健壮性。
在处理异常的路上,我们才刚刚起步。