一种对于laravel异常作为返回的解决方式
- 我们假定一个场景,用户注册, 需要参数
- 参数名 解释 类型 是否必填 mobile 用户手机号码 字符串 必填 sms_code 短信验证码 整数 必填 password 用户密码 字符串 用户密码 re_password 重复用户密码 整数 重复用户密码
- 以下列举会出现的问题情况
- 用户短信不匹配
- 用户短信类型错误
- 用户密码不相同
- 手机号已经使用过 直接登录即可
- 用户密码类型不符合要求
- 短信验证码不能为空
- 手机号码不能为空
- 两个密码都不能为空
- 现在已知会复用的场景有 会在别的业务内有相同错误的类型 (具体业务不做赘述,业务不同,理解不同)
- 短信验证 checkSms 验证短信验证码是否正确 类型是否匹配
- 修改密码 提示密码类型错误等场景
- 这里假定大家都不是大佬 业务有藕合 处理方案如下 (以下代码仅在checkSms下进行)
- 在checkSms函数里面直接
//第一次写文档 不会用markdown 你也可以用 response出去 这样浅显易懂 | |
exit(json_encode(['code'=>-1,'msg'=>'短信验证码错误'])); |
- 每处都做判断
if (false === checkSms($mobile,$code,$type)){ | |
exit(json_encode(['code'=>-1,'msg'=>'短信验证码错误'])); | |
} |
- 看看我的方式 (这句要怎么加粗啊)
- 已知你有三套业务 且每套业务包含N个子模块 (别杠微服务/跨语言等,杠就是你赢 /狗头)
- 现在出了问题 前端告诉你 code=-1 message=>’系统错误 || 需要登录 || 商品查询失败 || 短信失败 等各种错误信息 ‘
- 你什么心情????????????? 开始到处找,这个message在哪, 谁写的 ,什么时候写的,到底是哪个等
- 业务有藕合, 你在你的业务里面用的某一个service(仅做伪例子)内的action 发现抛出了一个你不清楚的异常,你去问,贴日志
- 结果必然是 你找的人去执行我上面说的那条,依次递归. 直到
Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 9216 bytes) in your problem
- 我就找不到在哪,我就是懵 日常维护老项目的同学岂不是人都没了… 为什么大家都是接盘,我却过得这么难
- 与其到处去找不如直接告诉我是怎么回事,但是你又很难保证大家写的错误信息内容都是一样的
- 首先我们明白一个道理 你写代码最害怕的是什么 是bug吗? 不是
- 是这个
- 你问我错哪了? 我怎么知道!!!!! 我就知道肯定报错了 抛出异常了 ,程序停了啊!!!
- ok 那么我们知道了一个道理,当程序抛出异常的时候,项目就会停掉.
- 同时我们也明白了一件事 叫做:
- 假定我们有一个业务模块叫做 User 里面包含了一个控制器叫做 AuthController
- 内部需要完成一个login的行为
namespace App\Http\Controllers\Application\Auth; | |
use App\Http\Controllers\Controller; | |
use App\Http\Requests\Application\Auth\LoginRequest; | |
use Illuminate\Http\JsonResponse; | |
class AuthController extends Controller | |
{ | |
// | |
public function index(LoginRequest $loginRequest): JsonResponse | |
{ | |
$data = $loginRequest->all(); | |
//todo 验证是否登录成功 | |
//todo 登录成功之后需要从返回里面获取token 和 userInfo | |
//todo 记录日志等行为 | |
//todo 返回前端 | |
return $this->success('登陆成功', compact('token', 'user')); | |
} | |
} |
- 一般会怎么做
if (!empty(login($username,$password))){ //todo 登录成功 //tonext}
- 一旦出错 怎么办?????? 开始
if(1){ | |
if(1){ | |
if(1){ | |
if(1){ | |
//建议这里直接用来测光速到底是多少 ,因为需求是无限长的 | |
//并且你知道到底是什么问题,什么业务返回来的,到底的意思是什么嘛? (突然成为派大星 ) | |
} | |
} | |
} | |
} |
- 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。如果你觉得 上面这个方案或者类似这个方案很棒,那我收回刚才那张图
- 我们上面已知程序抛出异常就会停掉,除非你继续catch 然后抛什么出来??????怕不是万能交税
- 1.我们设计让我们的程序听话,怎么听话,让他犯错自己会停,还会告诉你怎么回事
- 2.怎么实现,这么做的意义是什么
- 3.如何实现,这样做有什么别的意义没
- 4.性能损耗问题
- 回答问题
- 1.你是开发,程序是你的 你必须说什么让他听什么
- 2.此处仅做流程展示,最后会直接贴代码加注释,如果没耐心可以直接翻最后 创建异常应该都ok吧,不ok 就去看文档 你可以停在这了
namespace App\Exceptions; | |
use Exception; | |
class XxxException extends Exception{ | |
/** | |
* @Message('短信验证码错误') | |
*/ | |
const SMS_CODE_IS_ERROR = '300000000'; | |
/** | |
* @Message('短信验证码类型错误') | |
*/ | |
const SMS_CODE_TYPE_IS_ERROR = '300000001'; | |
/** | |
* @Message('短信验证码不存在') | |
*/ | |
const SMS_CODE_IS_NOT_EXISTS = '300000002';} | |
- 伪代码
//todo 验证码类型错误 | |
throw new XxxException(XxxException::SMS_CODE_IS_ERROR); |
- 程序现在是不是应该停下来了,因为当你exception的时候 下面代码不会执行了
- 但是新的问题出现了,如果这样抛出异常,前端怎么办???????????
- 此处小声bb 前端处理不了跟我什么关系啊,我是后台啊,你有问题找前端啊.
- 那么我们假设一下 如果我们告诉前端的是
{code:'300000000','message:'短信验证码错误'} | |
- 是不是就很舒服了,前后端是一家 怎么能闹脾气呢
- 那么如果 Code统一,请问出问题你在发愁什么? 是你的phpstorm不存在属性追踪吗?
- 怎么实现???????????????
- 你问我,我也不到啊 我只能给你这个啊
namespace App\Exceptions; | |
use App\Factory\ParseException; //解释异常的工厂 嫌弃名字长就没加Factory | |
use App\Traits\ResponseTrait; //这个是我自己写的一个简单的trait 也会一并贴上去 | |
use Illuminate\Database\Eloquent\ModelNotFoundException; | |
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Str; | |
use Illuminate\Validation\ValidationException; | |
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; | |
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |
use Throwable; | |
class Handler extends ExceptionHandler | |
{ | |
use ResponseTrait; | |
/** | |
* A list of the exception types that are not reported. | |
* | |
* @var array | |
*/ | |
protected $dontReport = [ | |
// | |
]; | |
/** | |
* A list of the inputs that are never flashed for validation exceptions. | |
* | |
* @var array | |
*/ | |
protected $dontFlash = [ | |
'current_password', | |
'password', | |
'password_confirmation', | |
]; | |
/** | |
* Register the exception handling callbacks for the application. | |
* | |
* @return void | |
*/ | |
public function register() | |
{ | |
$this->reportable(function (Throwable $e) { | |
// | |
}); | |
} | |
public function render($request, $e) | |
{ | |
//数据库没查到数据或者数据是softdelete | |
if ($e instanceof ModelNotFoundException) { | |
return $this->error('500', '数据不存在或已删除'); | |
} | |
//非允许请求方式 | |
if ($e instanceof MethodNotAllowedHttpException) { | |
return $this->error('422', '请求方式错误'); | |
} | |
//验证失败 | |
if ($e instanceof ValidationException) { | |
return $this->error(412, current(current($e->errors()))); | |
} | |
//这里是为了兼容其他的一些错误 | |
if (Str::length($e->getMessage()) > 1 && Str::length($e->getCode()) > 1) { | |
return $this->error($e->getCode(), $e->getMessage()); | |
} | |
//处理我们自己的错误 | |
$result = ParseException::parseException($e); | |
//这里判断的原因很简单 因为可能这个code没有按照规范声明 | |
if (is_array($result)) { | |
return $this->error($result['code'], $result['message']); | |
} | |
// Object Not Found 你懂我意思吧? | |
if ($e instanceof NotFoundHttpException) { | |
return $this->error('404', '页面路径不存在'); | |
} | |
//这里可以根据自己是否需要做兜底而决定是否兜底 | |
} | |
} |
parseException
namespace App\Factory; | |
use Illuminate\Support\Facades\Log; | |
class ParseException | |
{ | |
public static function parseException(\Throwable $exception) | |
{ | |
//注解 不懂得话建议直接看文档->反射 ,我讲不明白这个东西 | |
$annotation = new \ReflectionClass($exception); | |
//翻转 成code->constant | |
$values = array_flip($annotation->getConstants()); | |
if (empty($values)) { | |
return false; | |
} | |
//拿到对应的constant | |
$constant = $values[$exception->getMessage()]; | |
//constant反射 | |
$annotation_text = new \ReflectionClassConstant($exception, $constant); | |
//获取属性注释内容 | |
$comment = $annotation_text->getDocComment(); | |
try { | |
//正则大法好 建议留意此处 | |
preg_match("/Message\(\'(.*?)\'\)(\\r\\n|\\r|\\n)/U", $comment, $result); | |
} catch (\Throwable $e) { | |
return false; | |
} | |
if (false === isset($result[1])) { | |
return false; | |
} | |
return [ | |
'code' => $exception->getMessage(), | |
'message' => $result[1] | |
]; | |
} | |
} |
- 不要问我要 ResponseTrait 我相信一个简单的 响应实现你是ok的
- 这样实现的意义就是为了不管谁接手项目前端后端 看到错误信息一目了然,就算某天领导说不要需要告诉用户短信什么错了,就告诉他你短信错了,你只需要去改constant而已!
- 并且可读性高,ide支持 ,如果你觉得不合适,那我没辙了 ,我尽力了
- 性能损耗
- 目前没发现很明显的性能损耗,给出的调优方案也是 如果可以的话注解的类的属性列表(让你留意的地方)可以做缓存而已 ,(因为我目前不需要去考虑这个,laravels大法好)
- 不出意外的话我的代码你拿着直接贴进去就可以用,但是我不建议你这么做,因为一次吃饱不代表能一直吃饱,我希望你能清楚起码也要点赞,不能白嫖这个道理