第6章 AOP与全局异常处理
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
- 记录日志
- 做出统一的异常处理
运行结果
<hr>
error_code错误码
1 999 未知错误 2 1 开头为通用错误 3 2 商品类错误 4 3 主题类错误 5 4 Banner类错误 6 5 类目类错误 7 6 用户类错误 8 8 订单类错误 9 10 10000 通用参数错误 11 10001 资源未找到 12 10002 未授权(令牌不合法) 13 10003 尝试非法操作(自己的令牌操作其他人数据) 14 10004 授权失败(第三方应用账号登陆失败) 15 10005 授权失败(服务器缓存异常) 16 17 18 20000 请求商品不存在 19 20 30000 请求主题不存在 21 22 40000 Banner不存在 23 24 50000 类目不存在 25 26 60000 用户不存在 27 60001 用户地址不存在 28 29 80000 订单不存在 30 80001 订单中的商品不存在,可能已被删除 31 80002 订单还未支付,却尝试发货 32 80003 订单已支付过
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 }
6-6.2 thinkphp5定义日志的位置
在根目录的runtime/log/ 文件夹下面
define('LOG_PATH', __DIR__ . '/../log/'); //
6-7 在全局异常处理中加入日志记录 09:13
具体可以参考手册里的说明:
https://www.kancloud.cn/manual/thinkphp5/118127
可以关闭日志写入
1 'log' => [ 2 // 可以关闭日志写入 3 'type' => 'test', 4 ],
6-7.2 默认的话记录的日志级别是INFO,也可以指定日志级别:
Log::record('测试日志信息,这是警告级别','notice');
日志级别
ThinkPHP对系统的日志按照级别来分类,并且这个日志级别完全可以自己定义,系统内部使用的级别包括:
- log
- error
- notice
- info
- debug
- 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/'); //
控制器里临时抛出服务器异常
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, //日志生成的路径 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,
访问:
已经写入日志中:
'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里定义的信息