第6章 AOP与全局异常处理6.5-6.11 慕课网微信小程序开发学习笔记
6-1 正确理解异常处理流程 13:23
6-2 固有的处理异常的思维模式与流程 14:23
6-3 理清思路,总结异常的分类_ 06:33
6-4 实现自定义全局异常处理 上 17:04
6-5 实现自定义全局异常处理 下 16:04
6-6 ThinkPHP5中的日志系统 14:34
6-7 在全局异常处理中加入日志记录 09:13
6-8 全局异常处理的应用 上 25:23
6-9 全局异常处理的应用 中 12:04
6-10 全局异常处理的应用 下 04:16
6-11 本章小结与AOP思想 12:56
6-5 实现自定义全局异常处理 下 16:04
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/8
6 * Time: 15:58
7 */
8
9 namespace app\api\controller\v1;
10 use app\api\model\Banner as BannerModel;
11 use app\api\validate\IDMustBePostiveInt;
12 use app\lib\exception\BannerMissException;
13
14 class Banner
15 {
16 /**
17 * 获取指定id的banner信息
18 * @url /banner/:id
19 * @http GET
20 * @id banner的id号
21 */
22 public function getBanner($id)
23 {
24
25 (new IDMustBePostiveInt())->goCheck(); //验证$id是否为正整数
26 $banner = BannerModel::getBannerById($id);//调用model
27 if (!$banner){
28 throw new BannerMissException(); //判断结果不存在,抛出异常
29 }
30 // return $banner;
31 }
32 }
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Hiama
5 * Date: 2018/7/12
6 * Time: 1:16
7 */
8
9 namespace app\api\model;
10 class Banner
11 {
12 public static function getBannerById($id){
13 return null;
14 }
15 }
6-5-1.3 exceptionHandler.php判断异常类型,并抛出相应的异常
所以ExceptionHandler最终就会抛出BannerMissException里定义的code,msg和errorCode三个属性信息
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/12
6 * Time: 3:11
7 */
8
9 namespace app\lib\exception;
10 use Exception;
11 use think\exception\Handle;
12 use think\Request;
13
14 class ExceptionHandler extends Handle
15 {
16 private $code;
17 private $msg;
18 private $errorCode;
19
20 public function render(Exception $e)
21 {
22 if($e instanceof BaseException){
23 $this->code = $e->code;
24 $this->msg = $e->msg;
25 $this->errorCode = $e->errorCode;
26 }
27 else{
28 $this->code = 500;
29 $this->msg = '服务器错误,不想给你看';
30 $this->errorCode = 999;
31 }
32 $request = Request::instance();
33 $result = array(
34 'msg' => $this->msg,
35 'error_code' => $this->errorCode,
36 'request_url' => $request->url()
37 );
38 return json($result,$this->code);
39 }
40 }
<?php
/**
* Created by Haima.
* Author:Haima
* QQ:228654416
* Date: 2018/7/12
* Time: 20:48
*/
namespace app\lib\exception;
class BannerMissException extends BaseException
{
public $code = 404;
public $msg = '请求的Banner不存在';
public $errrCode = 40000;
}
1 <?php
2 /**
3 * Created by Haima.
4 * Author:Haima
5 * QQ:228654416
6 * Date: 2018/7/12
7 * Time: 20:49
8 */
9
10 namespace app\lib\exception;
11
12
13 use think\Exception;
14
15 class BaseException extends Exception
16 {
17 //HTTP 状态码 404,200
18 public $code = 400;
19
20 //错误的具体信息
21 public $msg = '参数错误';
22
23 //自定义的错误码
24 public $errorCode = 10000;
25 }
返回BannerMissException 里定义的三个属性值
6-6 ThinkPHP5中的日志系统 14:34
6-6.1 thinkphp5定义常量的位置
具体可以参考手册里的说明:
https://www.kancloud.cn/manual/thinkphp5/118127
6-7.1 关闭日志写入的方法,在config.php里,
1 'log' => [
2 // 可以关闭日志写入
3 'type' => 'test',
4 ],
6-7.2 默认的话记录的日志级别是INFO,也可以指定日志级别:
Log::record('测试日志信息,这是警告级别','notice');
日志级别
ThinkPHP对系统的日志按照级别来分类,并且这个日志级别完全可以自己定义,系统内部使用的级别包括:
- log 常规日志,用于记录日志
- error 错误,一般会导致程序的终止
- notice 警告,程序可以运行但是还不够完美的错误
- info 信息,程序输出信息
- debug 调试,用于调试信息
- sql SQL语句,用于SQL记录,只在数据库的调试模式开启时有效
系统提供了不同日志级别的快速记录方法,例如:
1 Log::error('错误信息');
2 Log::info('日志信息');
3 // 和下面的用法等效
4 Log::record('错误信息','error');
5 Log::record('日志信息','info');
还封装了一个助手函数用于日志记录,例如:
1 trace('错误信息','error');
2 trace('日志信息','info');
自定义生成日志的例子:
config.php里关闭了系统自动生成日志
修改thinkphp生成日志的位置
在public/index.php里
define('LOG_PATH', __DIR__ . '/../log/'); //修改thinkphp生成日志的位置
在Banner.php控制器里临时抛出服务器异常做测试
ExceptionHandler里封装服务器异常recordErrorLog() 生成日志的函数
ExceptionHandler里判断抛出的异常是服务器异常,走else里的代码,并调用封装的recordErrorLog() 生成日志的函数,写日志
因为config.php里关闭了系统自动写日志,所以在recordErrorLog() 函数里要初始化一下日志
Log::record() 记录日志信息到内存 上面需要引入 use think\Log;
Log::record('测试日志信息,这是警告级别','notice');
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/12
6 * Time: 3:11
7 */
8
9 namespace app\lib\exception;
10 use Exception;
11 use think\exception\Handle;
12 use think\Log;
13 use think\Request;
14
15 class ExceptionHandler extends Handle
16 {
17 private $code;
18 private $msg;
19 private $errorCode;
20
21 public function render(Exception $e)
22 {
23 if($e instanceof BaseException){
24 $this->code = $e->code;
25 $this->msg = $e->msg;
26 $this->errorCode = $e->errorCode;
27 }
28 else{
29 $this->code = 500;
30 $this->msg = '服务器错误,不想给你看';
31 $this->errorCode = 999;
32 $this->recordErrorLog($e); //调用服务器异常错误
33 }
34 $request = Request::instance();
35 $result = array(
36 'msg' => $this->msg,
37 'error_code' => $this->errorCode,
38 'request_url' => $request->url()
39 );
40 return json($result,$this->code);
41 }
42
43 //服务器异常错误
44 private function recordErrorLog(Exception $e){
45 Log::init([
46 'type'=>'File', //生成的类型是文件
47 'path'=>LOG_PATH, //日志生成的路径 ,这里LOG_PATH的路径已经在public/index.php里定义了
48 'level'=>['error'] // 日志记录级别,使用数组表示
49 ]);
50 Log::record($e->getMessage(),'error'); //写入日志
51 }
52 }
给控制器发送请求:
此时已经在项目根目录里自动成功日志目录了
6-8 全局异常处理的应用 上 25:23
读取application\config.php配置文件里内容的方法
方法一:
config('app_debug')
方法二:
Config::get('app_debug');
注意:
配置文件里的内容只能读取它里的某些配置信息,不要用它来做数据保存,
如果要保存数据可以写入数据库,redis缓存,thinkphp自提缓存,或者其它缓存的地方,或者保存到全局变量里
前端人员显示报错的页面,后端人员显示报错的json信息
思路:
在ExceptionHandler.php里的服务器异常里做判断,
判断配置里debug是否为true
(把它做为一个开关,通常上线后会关闭debug调适,这样服务器异常就会开启,异常就会写入日志里,当然你也可以自定义一个开关),
如果debug为ture就显示tp5框架自身的报错页面,给前台开发人员看,
否则就显示json的报错信息并写入日志中,给后端的开发人员看.
ExceptionHandler.php里读取debug值做为写日志信息的开关判断
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/12
6 * Time: 3:11
7 */
8
9 namespace app\lib\exception;
10 use Exception;
11 use think\exception\Handle;
12 use think\Log;
13 use think\Request;
14
15 class ExceptionHandler extends Handle
16 {
17 private $code;
18 private $msg;
19 private $errorCode;
20
21 public function render(Exception $e)
22 {
23 if($e instanceof BaseException){
24 //如果是自定义异常,则控制http状态码,不需要记录日志
25 //因为这些通常是因为客户端传递参数错误或者是用户请求造成的异常
26 //不应当记录日志
27 $this->code = $e->code;
28 $this->msg = $e->msg;
29 $this->errorCode = $e->errorCode;
30 }
31 else{
32 // 如果是服务器未处理的异常,将http状态码设置为500,并记录日志
33 //Config::get('app_debug'); //获取config.php里的配置信息
34 if (config('app_debug')){ //获取config.php里的配置信息如果是true走这里
35 // 如果是前台调适人员看就显示json格式错误
36 // 调试状态下需要显示TP默认的异常页面,因为TP的默认就是页面
37 // 很容易看出问题
38 return parent::render($e); //调用thinkphp5默认的报错页面
39 }
40 //如果是后台调适人员看就显示json格式错误
41 $this->code = 500;
42 $this->msg = '服务器错误,不想给你看';
43 $this->errorCode = 999;
44 $this->recordErrorLog($e); //调用服务器异常错误
45 }
46 $request = Request::instance();
47 $result = array(
48 'msg' => $this->msg,
49 'error_code' => $this->errorCode,
50 'request_url' => $request->url()
51 );
52 return json($result,$this->code);
53 }
54
55 //服务器异常错误
56 private function recordErrorLog(Exception $e){
57 Log::init([
58 'type'=>'File', //生成的类型是文件
59 'path'=>LOG_PATH, //日志生成的路径
60 'level'=>['error'] // 日志记录级别,使用数组表示
61 ]);
62 Log::record($e->getMessage(),'error'); //写入日志
63 }
64 }
为false时:写入json日志
'app_debug' => false,
访问:
已经写入日志中:
为true时:显示报错页面,并不会写入自定义的log目录的日志中
'app_debug' => true,
自定义的异常不显示页面报错
自定义的异常就显示我们自定义的json形式的报错就可以了,没有必要再显示页面的报错了(没有意义),所以就不用再做if判断了,
应用实例:
思路:
所有的整个参数验证层的错误处理都集中在BaseValidate这里,它承担了所有验证层的验证工作.是唯的验证入口
当客户访问z.com/banner/0.1时,banner.php/getBanner调用BaseValidate里的goCheck()方法验证,
goCheck()方法通过$this->check($params)调用IDMustBePostiveInt验证传入的$id是不是正整数
(IDMustBePostiveInt已经继承BaseValidate,BaseValidate 又继承 Validate,所以可以用$this->check($params)直接调用到),
当传入的参数不正确,BaseValidate验证不通过,抛出异常(这里需要它返回一个json结构体的消息,还要指明错误的原因,而不应该被反回服务器错误信息给隐藏掉),
ExceptionHandler捕捉到异常,并判断是异常类型后,抛出相应的异常信息.
优势:BaseValidate只管抛出异常,异常的类型交由ExceptionHandler来判断,然后决定抛出什么样的异常.
banner.php 里:
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/8
6 * Time: 15:58
7 */
8
9 namespace app\api\controller\v1;
10 use app\api\model\Banner as BannerModel;
11 use app\api\validate\IDMustBePostiveInt;
12 use app\lib\exception\BannerMissException;
13 use think\Exception;
14
15 class Banner
16 {
17 /**
18 * 获取指定id的banner信息
19 * @url /banner/:id
20 * @http GET
21 * @id banner的id号
22 */
23 public function getBanner($id)
24 {
25
26 (new IDMustBePostiveInt())->goCheck(); //验证$id是否为正整数
27 $banner = BannerModel::getBannerById($id);//调用model
28 if (!$banner){
29 // throw new BannerMissException(); //判断结果不存在,抛出异常
30 throw new Exception('服务器内部异常'); //临时抛出服务器异常测试用
31 }
32 // return $banner;
33 }
34 }
BaseValidate里:
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Administrator
5 * Date: 2018/7/10
6 * Time: 21:49
7 */
8
9 namespace app\api\validate;
10
11 use app\lib\exception\parameterException;
12 use think\Exception;
13 use think\Request;
14 use think\Validate;
15
16 class BaseValidate extends Validate
17 {
18
19 //所有的整个参数验证层的错误处理都集中在这里,承担了所有验证层的验证工作
20 public function goCheck()
21 {
22 //获取http传入的参数
23 //方法一:
24 // $request = Request::instance();
25 // $params = $request->param();
26 // 方法二:
27 $params = Request::instance()->param();
28 //对参数进行校验
29 if(!$this->check($params)){
30
31 // 这里需要它返回一个json结构体的消息,还要指明错误的原因,
32 // 而不应该被反回服务器错误信息给隐藏掉
33 $e = new parameterException();
34 //用验证器里定义的错误信息重写parameterException下的$msg属性,
35 //这样就能正常报出验证器里定义的异常信息了
36 $e->msg=$this->error;
37 // 抛出的必需是一个Exception异常,
38 // 因为parameterException必需继承自BaseException,而BaseException已经继承了Exception.
39 // 所以parameterException自然也就继承了Exception
40 throw $e; //优势,只管抛出异常,异常的类型交由ExceptionHandler来判断,然后决定抛出什么样的异常
41
42 /* $error = $this->error;
43 throw new Exception($error);*/
44 }
45 else{
46 return true;
47 }
48 }
49 }
IDMustBePostiveInt验证器里
验证必需为正整数
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Administrator
5 * Date: 2018/7/10
6 * Time: 12:18
7 */
8
9 namespace app\api\validate;
10
11 class IDMustBePostiveInt extends BaseValidate
12 {
13 protected $rule = [
14 'id' => 'require|isPositiveInteger'
15 ];
16
17 protected function isPositiveInteger($value,$rule='', $data='', $field='')
18 {
19 if(is_numeric($value) && is_int($value + 0) && ($value + 0) >0)
20 {
21 return true;
22 }
23 else
24 {
25 return $field.'必需是正整';
26 }
27 }
28 }
新建一个parameterException.php异常处理器,并继承BaseException
(当参数验证不通过时,它将被BaseValidate抛出异常)
1 <?php
2 /**
3 * Created by Haima.
4 * Author:Haima
5 * QQ:228654416
6 * Date: 2018/7/15
7 * Time: 10:48
8 */
9
10 namespace app\lib\exception;
11
12
13 class parameterException extends BaseException
14 {
15 public $code = 400;
16 //随便定义一个通用的提示信息,之后validate抛出异常信息时,会被重写成validate里定义的错误信息
17 public $msg = '参数错误';
18 //通过参数错误,凡是被验证层检查出来的错误都返回一个10000
19 public $errorCode = 10000;
20 }
ExceptionHandler里判断异常类型,并抛出相应的异常信息
ExceptionHandler里捕捉到$e异常后,判断是属于BaseExcetion参数错误异常,
捕捉到的BaseValidate里抛出的parameterException里的异常
所有就把parameterException的异常信息给继承过来,BaseValidate又改写了$msg属性,BaseValidate里的$this->error取的又是IDMustBePostiveInt里定义的错误信息
最终于会抛出验证器IDMustBePostiveInt里定义的错误信息
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Haima
5 * Date: 2018/7/12
6 * Time: 3:11
7 */
8
9 namespace app\lib\exception;
10 use Exception;
11 use think\exception\Handle;
12 use think\Log;
13 use think\Request;
14
15 class ExceptionHandler extends Handle
16 {
17 private $code;
18 private $msg;
19 private $errorCode;
20
21 public function render(Exception $e)
22 {
23 if($e instanceof BaseException){
24 //如果是自定义异常,则控制http状态码,不需要记录日志
25 //因为这些通常是因为客户端传递参数错误或者是用户请求造成的异常
26 //不应当记录日志
27 //这里取的是当前抛出异常的控制器里定义的异常信息
28 $this->code = $e->code;
29 $this->msg = $e->msg;
30 $this->errorCode = $e->errorCode;
31 }
32 else{
33 // 如果是服务器未处理的异常,将http状态码设置为500,并记录日志
34 //Config::get('app_debug'); //获取config.php里的配置信息
35 if (config('app_debug')){ //获取config.php里的配置信息如果是true走这里
36 // 如果是前台调适人员看就显示json格式错误
37 // 调试状态下需要显示TP默认的异常页面,因为TP的默认就是页面
38 // 很容易看出问题
39 return parent::render($e); //调用thinkphp5默认的报错页面
40 }
41 //如果是后台调适人员看就显示json格式错误
42 $this->code = 500;
43 $this->msg = '服务器错误,不想给你看';
44 $this->errorCode = 999;
45 $this->recordErrorLog($e); //调用服务器异常错误
46 }
47 $request = Request::instance();
48 $result = array(
49 'msg' => $this->msg,
50 'error_code' => $this->errorCode,
51 'request_url' => $request->url()
52 );
53 return json($result,$this->code);
54 }
55
56 //服务器异常错误
57 private function recordErrorLog(Exception $e){
58 Log::init([
59 'type'=>'File', //生成的类型是文件
60 'path'=>LOG_PATH, //日志生成的路径
61 'level'=>['error'] // 日志记录级别,使用数组表示
62 ]);
63 Log::record($e->getMessage(),'error'); //写入日志
64 }
65 }
访问结果:
报出的就是validate里定义的错误信息
$code 和 $errorCode 就是 parameterException里定义的信息
6-9 全局异常处理的应用 中 12:04
异常处理优化代码:
思路:
parameterException继承了BaseException,所以它自然也继承了父类里的__construct构造函数,
BaseValidate里实例化parameterException,并通过传参给,parameterException里的__construct构造函数重写了parameterException里的$msg属性,
ExceptionHandler里判断异常后,抛出异常时取的是当前被抛出异常的 parameterException里的$msg属性信息.
BaseValidate里:
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: Administrator
5 * Date: 2018/7/10
6 * Time: 21:49
7 */
8
9 namespace app\api\validate;
10
11 use app\lib\exception\parameterException;
12 use think\Exception;
13 use think\Request;
14 use think\Validate;
15
16 class BaseValidate extends Validate
17 {
18
19 //所有的整个参数验证层的错误处理都集中在这里,承担了所有验证层的验证工作
20 public function goCheck()
21 {
22 //获取http传入的参数
23 //方法一:
24 // $request = Request::instance();
25 // $params = $request->param();
26 // 方法二:
27 $params = Request::instance()->param();
28 //对参数进行校验
29 if(!$this->check($params)){
30
31 //优化下面的代码,实例化时调用传入参数重写parameterException下的$msg属性
32 //而不用$e->msg=$this->error;这样的方式重写
33 $e = new parameterException([
34 'msg'=>$this->error //只改写msg 其它两个值还用原始值
35 ]);
36
37 throw $e; //优势,只管抛出异常,异常的类型交由ExceptionHandler来判断,然后决定抛出什么样的异常
38
39 }
40 else{
41 return true;
42 }
43 }
44 }
BaseException里:
1 <?php
2 /**
3 * Created by Haima.
4 * Author:Haima
5 * QQ:228654416
6 * Date: 2018/7/12
7 * Time: 20:49
8 */
9
10 namespace app\lib\exception;
11
12 use think\Exception;
13
14 class BaseException extends Exception
15 {
16 //HTTP 状态码 404,200
17 public $code = 400;
18
19 //错误的具体信息
20 public $msg = '参数错误';
21
22 //自定义的错误码
23 public $errorCode = 10000;
24
25 public function __construct($param = []){
26 if (!is_array($param)){
27 //下面两种处理方式都可以,看个人怎么理解了.
28 //方法一:如果传来的不是数组就返回回去,认为不是要改写属性
29 return;
30 //方法二: 如果不是数组就报错,强制让传入数组
31 // throw new Exception('参数必需是数组');
32 }
33 if(array_key_exists('code',$param)){
34 $this->code = $param['code'];
35 }
36
37 if(array_key_exists('msg',$param)){
38 $this->msg = $param['msg'];
39 }
40
41 if(array_key_exists('errorCode',$param)){
42 $this->errorCode = $param['error'];
43 }
44 }
45 }
parameterException里:
1 <?php
2 /**
3 * Created by Haima.
4 * Author:Haima
5 * QQ:228654416
6 * Date: 2018/7/15
7 * Time: 10:48
8 */
9
10 namespace app\lib\exception;
11
12
13 use think\Exception;
14
15 class parameterException extends BaseException
16 {
17 public $code = 400;
18 //随便定义一个通用的提示信息,之后validate抛出异常信息时,会被重写成validate里定义的错误信息
19 public $msg = '参数错误';
20 //通过参数错误,凡是被验证层检查出来的错误都返回一个10000
21 public $errorCode = 10000;
22
23
24 }
ExceptionHandler里判断并抛出异常
访问结果:
6-10 全局异常处理的应用 下 04:16
优化代码:
修改BaseValidate里加上batch(),命苦支持批量验证
IDMustBePosttiveInt里再加入num的验证
访问一下:
z.com/banner/0.1?num=4
6-11 本章小结与AOP思想 12:56
1.构建统一的业务处理层(如验证层,异常处理层).
2.努力重构,写出更优秀的代码
3.AOP面向切面编程:
不要把代码写的太直白,要站在一个更高的角度,用比较抽象的方式,统一的总体的来处理处某一个问题,
举个例子:
AOP思想,就相当于电影院里的检票员检票的流程,只有一个检票口,每个来看电影的人统一在检票口进行检票,这样就很节省资源了.
唯一的入口,统一的检票处理.