小白折腾服务器(七):自定义接口错误响应格式

上一篇是使用后置中间件自定义接口成功返回格式
这篇来写处理异常,自定义接口错误返回格式

基础小知识
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是一个很灵活的异常类,可以自定义多种参数。

  1. 在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);
    }
}
  1. 在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。

  1. 在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;
    }
}
本帖由系统于 2周前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 7
kinyou

认真的人最可爱

1个月前
aen233

@kinyou :relaxed:蟹蟹呀

1个月前
GucciLee

学习了, 感谢

4周前
guanhui07

返回的字段 信息 和我差不多

4周前
十七岁程序员想当歌手

你用的是 dingo / api 吗?我这里测试不成功。

2周前
aen233

@十七岁程序员想当歌手 没有用dingo,就是laravel直接orm查询返回 和使用aipresoure两种

2周前

@十七岁程序员想当歌手 https://mikecoder.cn/post/145/ 把 ding/api 的异常改下就可以

1周前

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!