小白折腾服务器(七):自定义接口错误响应格式
上一篇是使用后置中间件自定义接口成功返回格式
这篇来写处理异常,自定义接口错误返回格式
基础小知识
laravel处理异常的位置在app/Exceptions
这个目录,如果新建异常类,就在这个目录
这个目录中,最重要的是Handler.php
这个文件,如何处理渲染异常,是这个类的rander方法。如果你需要自定义错误输出,其实就是重写这个rander方法。
leo老师在电商实战那本书里第5.7节-优雅地处理异常 里面教过,
Laravel 5.5 之后支持在异常类中定义 render() 方法,该异常被触发时系统会调用 render() 方法来输出,我们在 render() 里判断如果是 AJAX 请求则返回 JSON 格式的数据,否则就返回一个错误页面。
这就是说,我们只要新建一个 xxxException
类,让它继承自 Exception
,
在这个 xxxException
类中,定义一个构造函数,用来接收参数(如$message, $code),
再定义一个render方法,处理接收的参数,组装成你想要的格式(如$content = ['message' => $this->message, 'code' => $this->code]
),最后返回return response()->json($content, $status);
,$content就是你自定义的异常输出内容,$status是http状态码。
然后在控制器方法中,throw new xxxException('message',500)
。试一下,就是你自定义的错误格式了。
可以新建多个异常类,用来输出不同的异常。
最简单的异常类:
namespace App\Exceptions;
use Exception;
class SimpleException extends Exception
{
const HTTP_OK = 200;
const HTTP_ERROR = 500;
protected $data;
public function __construct($message, int $code = self::HTTP_OK, array $data = [])
{
$this->data = $data;
parent::__construct($message, $code);
}
public function render()
{
$content = [
'message' => $this->message,
'code' => $this->code,
'data' => $this->data ?? [],
'timestamp' => time()
];
$status = self::HTTP_ERROR;
return response()->json($content, $status);
}
}
如果不想新建异常类,只需要统一异常错误输出,就修改app/Exceptions/Handler中的render方法就好了。
public function render($request, Exception $exception)
{
// 这里通常要用到 instanceof 这个运算符,先判断再处理
// return parent::render($request, $exception); 注释掉这行
return response()->json($exception->getMessage(), 500);
}
做个小总结,自定义错误处理有两个方法(其实都一样)
1.新建一个异常类,在app/Exceptions/Handler中的render方法将所有异常都指向你新建的异常类。
2.直接修改app/Exceptions/Handler中的render方法,自定义异常格式。
第一种:BaseException
BaseException是一个很灵活的异常类,可以自定义多种参数。
- 在app/Exceptions下新建BaseException
namespace App\Exceptions;
use Exception;
class BaseException extends Exception
{
const HTTP_OK = 200;
protected $data;
protected $code;
public function __construct($data, int $code = self::HTTP_OK, array $meta = [])
{
// 第一个参数是data,是因为想兼容string和array两种数据结构
// 第二个参数默认取200,是因为公司前端框架限制,所以是status取200,错误码用code表示
// 如果第二个参数是任意httpStatus(如200,201,204,301,422,500),就只返回httpStatus,如果是自定义错误编码,(如600101,600102),就返回httpstatus为200,返回体中包含message和code。
// 第三个参数默认为空数组,如果在message和code之外,还需要返回数组,就传递第三个参数
$this->data = $data;
$this->code = $code;
$this->meta = $meta;
// parent::__construct($data, $code);
}
public function render()
{
$httpStatus = getHttpStatus();
$status = in_array($this->code, $httpStatus) ? $this->code : self::HTTP_OK;
$content = [];
if (is_array($this->data)) {
$content = $this->data;
}
if (is_string($this->data)) {
$content = in_array($this->code, $httpStatus)
? [
'message' => $this->data
]
: [
'message' => $this->data,
'code' => $this->code,
// 'timestamp' => time()
];
}
if ($this->meta) {
$content = array_add($content, 'meta', $this->meta);
}
return response($content, $status);
}
}
- 在helpers中增加函数:(或者直接写在这个异常类中,私有调用)
该函数是获取Symfony定义的所有Http状态码。比如200=HTTP_OK。function getHttpStatus() { $objClass = new \ReflectionClass(\Symfony\Component\HttpFoundation\Response::class); // 此处获取类中定义的全部常量 返回的是 [key=>value,...] 的数组,key是常量名,value是常量值 return array_values($objClass->getConstants()); }
3.基础使用
baseException($data, int $code=200, array $meta=[]);
第1个参数可以为string 或 array.
第2个参数默认为200,如果传的code是任意一个httpStatus,表示返回的http状态码(如404、500等),
如果是自定义错误码(非任意一个httpStatus,如1001、1002),则http状态码返回200,code码在json内容中返回
第3个参数默认为空数组。如果传第3个参数,将一起返回。
3.1 参数传string
throw new BaseException('都是好孩子');
Status: 200 OK
{
"message": "都是好孩子"
}
3.2 参数传string,code(自定义错误码,非httpStatus)
throw new BaseException('都是好孩子',1001);
Status: 200 OK
{
"message": "都是好孩子",
"code": 1001
}
3.3 参数传string,code(httpStatus)
throw new BaseException('都是好孩子', 404);
Status: 404 Not Found
{
"message": "都是好孩子"
}
3.4 参数传array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123']);
Status: 200 OK
{
"msg": "都是好孩子",
"code": "123"
}
3.5 参数传array,code(httpStatus)
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 403);
Status: 403 Forbidden
{
"msg": "都是好孩子",
"code": "123"
}
3.6 参数传string,code(httpStatus),array
throw new BaseException('都是好孩子', 422, ['msg' => '天是蓝的', 'code' => '24678']);
Status: 422 Unprocessable Entity
{
"message": "都是好孩子",
"meta": {
"msg": "天是蓝的",
"code": "24678"
}
}
3.7 参数传string,code(自定义错误码,非httpStatus),array
throw new BaseException('都是好孩子', 4567, ['msg' => '天是蓝的', 'code' => '24678']);
Status: 200 OK
{
"message": "都是好孩子",
"code": 4567,
"meta": {
"msg": "天是蓝的",
"code": “24678"
}
}
3.8 参数传array,code(自定义错误码,非httpStatus),array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 1234, ['msg' => '天是蓝的', 'code' => '24678']);
Status: 200 OK
{
"msg": "都是好孩子",
"code": "123",
"meta": {
"msg": "天是蓝的",
"code": "24678"
}
}
3.9 参数传array,code(自定义错误码,非httpStatus),array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 500, ['msg' => '天是蓝的', 'code' => '24678']);
Status: 500 Internal Server Error
{
"msg": "都是好孩子",
"code": "123",
"meta": {
"msg": "天是蓝的",
"code": "24678"
}
}
参数校验异常
使用laravel内置的ValidationException
1.在app/Exceptions/Handler中添加
public function render($request, Exception $exception)
{
//-------新加这4行---------
if ($exception instanceof ValidationException) {
$message = current(current(array_values($exception->errors())));
throw new BaseException($message, 4022); // 不加4022,会返回httpStatus=422;加4022是因为返回前端统一httpStatus为200,就在422中加了0
}
//------------------------
return parent::render($request, $exception);
}
2.基础使用
//控制器中,不需要额外抛出异常
public function index(Request $request)
{
Validator::make($request->all(), [
'file' => 'bail|required|file'
], [
'file.required' => '请上传文件'
])->validate();
}
输出:
//handler中不加4022
Status: 422 Unprocessable Entity
{
"message": "请上传文件"
}
//handler中加4022
Status: 200 OK
{
"message": "请上传文件",
"code": 4022,
}
处理其他异常
同参数校验异常,如处理FatalThrowableError(定义错误码为5678),然后故意写个语法错误。
同样不需要自己抛错,也不会出现报错大黑框
Status: 200 OK
{
"message": "Parse error: syntax error, unexpected 'dd' (T_STRING)",
"code": 5678
}
CodeException
为了项目统一规范,需统一管理code错误码,所以建立CodeException。
- 在app/Exceptions下新建error.php。返回错误信息数组。
<?php return [ 1001 =>'门前大桥下', 1002 =>'游过一群鸭' ];
2 . 在helpers中增加函数:
该函数是获取该errorCode相对应的errorMessage。
function getErrorMessage($code)
{
$err = require_once __DIR__.'/../app/Exceptions/error.php';
return $err[$code];
}
3 . 在app/Exceptions下新建CodeException
4 . 基础使用
throw new CodeException(1001);
// 返回
{
"message": "门前大桥下",
"code": 1001
}
有附加错误信息数组
throw new CodeException(1001,['info'=>'门前大桥下','text'=>'游过一群鸭']);
//返回
{
"message": "门前大桥下",
"code": 1001,
"data": {
"info": "门前大桥下",
"text": "游过一群鸭"
}
}
第二种 修改Handler
public function render($request, Exception $exception)
{
if ($exception instanceof BaseException
|| $exception instanceof HttpException
|| $exception instanceof ValidationException
|| $exception instanceof QueryException
|| $exception instanceof ModelNotFoundException
|| $exception instanceof ErrorException
) {
return $this->error($exception);
}
return parent::render($request, $exception);
}
/**
* 自定义错误输出
*
* @param $exception
*
* @return \Illuminate\Http\JsonResponse
*/
private function error($exception)
{
$statusCode = 500;
if ($exception instanceof ValidationException) {
$code = $exception->status;
$message = current(current(array_values($exception->errors())));
} else {
$code = $exception->getCode() ?: ($exception->statusCode ?? 404);
$message = $exception->getMessage();
}
$response = [
'id' => md5(uniqid()),
'code' => $code,
'status' => $statusCode,
'message' => $message,
'error' => 'ERROR',
];
if ($exception instanceof BaseException && $exception->getData()) {
$response['data'] = $exception->getData();
}
iuLog('error', 'Response Error: ', $response);
iuLog(PHP_EOL);
return response()->json($response, $statusCode, [], 320);
}
这里也保留了BaseException,但是写的要简单些,因为大部分逻辑写在Handler里面,这里只留了获取StatusCode 和 获取数组信息。使用和上面一样。
namespace App\Exceptions;
use Exception;
class BaseException extends Exception
{
protected $data;
/**
* Error constructor.
*
* @param string $message
* @param int $code
* @param array $data
*/
public function __construct($message = "", $code = 500, $data = [])
{
$this->data = $data;
parent::__construct($message, $code);
}
public function getStatusCode()
{
$objClass = new \ReflectionClass(\Symfony\Component\HttpFoundation\Response::class);
// 此处获取类中定义的全部常量 返回的是 [key=>value,...] 的数组
// key是常量名 value是常量值
// dd($objClass->getConstants());
$httpStatus = array_values($objClass->getConstants());
return in_array($this->code, $httpStatus) ? $this->code : 500;
}
public function getData()
{
return $this->data;
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
认真的人最可爱
@kinyou :relaxed:蟹蟹呀
学习了, 感谢
返回的字段 信息 和我差不多
你用的是
dingo / api
吗?我这里测试不成功。@十七岁程序员想当歌手 没有用dingo,就是laravel直接orm查询返回 和使用aipresoure两种
@十七岁程序员想当歌手 https://mikecoder.cn/post/145/ 把 ding/api 的异常改下就可以
哈哈哈,细心
那个 第一种:BaseException 1.里是不是少了个protected $meta。。还有想请教一下就是在
xxxException
类的render()方法是怎么被调用的。。还像没看到有调用render()方法呀...谢谢CodeException 的代码没贴出来耶
这里的不加4022应该返回的200的状态码吧,这个不是你在BaseException里面render方法里面定义的吗?怎么会变成422默认的参数验证的状态码呢?
我在想那些错误应该按照异常来处理,比如:代码错误、网络超时
还有比如:没有权限、活动结束、库存不足。这些应该是按照异常来处理,还是正常返回
抛出异常的时候,http状态码到底是200还是其他状态
感觉这个异常类也可以用来返回正确的响应啊
想法一致就来搜了,蓝后看完了所有文章发现是个宝藏程序员。不知有没有个人网站之类的,翻了一下你的github也没有找到,想交个朋友~~
好像发现要给问题,如果使用了作者的自定义返回成功返回格式,和这里的异常统一处理。当
httpStatus
为 200 时,成功返回格式化会对异常返回做格式处理。需要过滤一下: