响应格式的统一,对前端来说,是一种更好的体验。这里借鉴别人的地方,仅仅对 response 做统一格式响应。
规范的响应结构
code—— 包含一个整数类型的 HTTP 响应状态码。
status—— 包含文本:”success”,”fail” 或”error”。HTTP 状态响应码在 500-599 之间为”fail”,在 400-499 之间为”error”,其它均为”success”(例如:响应状态码为 1XX、2XX 和 3XX)。
message—— 当状态值为”fail” 和”error” 时有效,用于显示错误信息。参照国际化(il8n)标准,它可以包含信息号或者编码,可以只包含其中一个,或者同时包含并用分隔符隔开。
data—— 包含响应的 body。当状态值为”fail” 或”error” 时,data 仅包含错误原因或异常名称。
说明
整体响应结构设计参考如上,相对严格地遵守了 RESTful 设计准则,返回合理的 HTTP 状态码。
考虑到业务通常需要返回不同的 “业务描述处理结果”,在所有响应结构中都支持传入符合业务场景的 message。
整体响应结构设计参考如上,相对严格地遵守了 RESTful 设计准则,返回合理的 HTTP 状态码。
考虑到业务通常需要返回不同的“业务描述处理结果”,在所有响应结构中都支持传入符合业务场景的message
。
- data:
- 查询单条数据时直接返回对象结构,减少数据层级;
- 查询列表数据时返回数组结构;
- 创建或更新成功,返回修改后的数据;(也可以不返回数据直接返回空对象)
- 删除成功时返回空对象
- status:
- error, 客户端(前端)出错,HTTP 状态响应码在400-599之间。如,传入错误参数,访问不存在的数据资源等
- fail,服务端(后端)出错,HTTP 状态响应码在500-599之间。如,代码语法错误,空对象调用函数,连接数据库失败,undefined index等
- success, HTTP 响应状态码为1XX、2XX和3XX,用来表示业务处理成功。
- message: 描述执行的请求操作处理的结果;也可以支持国际化,根据实际业务需求来切换。
- code: HTTP 响应状态码;可以根据实际业务需求,调整成业务操作码
操作一波
新建 app/Support/Traits/ResponseTrait
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Support\Traits;
use App\Support\Response;
use Illuminate\Http\Request;
use Throwable;
/**
* Trait Helpers.
*
* @property \App\Support\Response $response
*/
trait ResponseTrait
{
public function __get($key)
{
$callable = [
'response',
];
if (in_array($key, $callable) && method_exists($this, $key)) {
return $this->$key();
}
throw new \ErrorException('Undefined property '.get_class($this).'::'.$key);
}
/**
* @return Response
*/
protected function response()
{
return app(Response::class);
}
/**
* Custom Normal Exception response.
*
* @param $request
* @param Throwable $e
*/
protected function prepareJsonResponse($request, Throwable $e)
{
// 要求请求头 header 中包含 /json 或 +json,如:Accept:application/json
// 或者是 ajax 请求,header 中包含 X-Requested-With:XMLHttpRequest;
$this->response->fail(
'',
$this->isHttpException($e) ? $e->getStatusCode() : 500,
$this->convertExceptionToArray($e),
$this->isHttpException($e) ? $e->getHeaders() : [],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
);
}
/**
* Custom Failed Validation Response.
*
* @param Request $request
* @param array $errors
*
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
protected function buildFailedValidationResponse(Request $request, array $errors)
{
if (isset(static::$responseBuilder)) {
return call_user_func(static::$responseBuilder, $request, $errors);
}
$this->response->fail('Validation error', 422, $errors);
}
}
再建 App/Support/Response
<?php
/*
* This file is part of the Jiannei/lumen-api-starter.
*
* (c) Jiannei <longjian.huang@foxmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace App\Support;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\HigherOrderTapProxy;
class Response
{
public function accepted(string $message = '')
{
return $this->success(null, $message, HttpResponse::HTTP_ACCEPTED);
}
public function created($data = null, string $message = '', string $location = '')
{
$response = $this->success($data, $message, HttpResponse::HTTP_CREATED);
if ($location) {
$response->header('Location', $location);
}
return $response;
}
public function errorBadRequest(?string $message = '')
{
$this->fail($message, HttpResponse::HTTP_BAD_REQUEST);
}
public function errorForbidden(string $message = '')
{
$this->fail($message, HttpResponse::HTTP_FORBIDDEN);
}
public function errorInternal(string $message = '')
{
$this->fail($message, HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
}
public function errorMethodNotAllowed(string $message = '')
{
$this->fail($message, HttpResponse::HTTP_METHOD_NOT_ALLOWED);
}
public function errorNotFound(string $message = '')
{
$this->fail($message, HttpResponse::HTTP_NOT_FOUND);
}
public function errorUnauthorized(string $message = '')
{
$this->fail($message, HttpResponse::HTTP_UNAUTHORIZED);
}
/**
* @param string $message
* @param int $code
* @param null $data
* @param array $header
* @param int $options
*
* @throws HttpResponseException
*/
public function fail(string $message = '', int $code = HttpResponse::HTTP_INTERNAL_SERVER_ERROR, $data = null, array $header = [], int $options = 0)
{
response()->json(
$this->formatData($data, $message, $code),
$code,
$header,
$options
)->throwResponse();
}
public function noContent(string $message = '')
{
return $this->success(null, $message, HttpResponse::HTTP_NO_CONTENT);
}
/**
* @param JsonResource|array|null $data
* @param string $message
* @param int $code
* @param array $headers
* @param int $option
*
* @return JsonResponse|JsonResource
*/
public function success($data = null, string $message = '', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
{
if (! $data instanceof JsonResource) {
return $this->formatArrayResponse($data, $message, $code, $headers, $option);
}
if ($data instanceof ResourceCollection && ($data->resource instanceof AbstractPaginator)) {
return $this->formatPaginatedResourceResponse(...func_get_args());
}
return $this->formatResourceResponse(...func_get_args());
}
/**
* Format normal array data.
*
* @param array|null $data
* @param string $message
* @param int $code
* @param array $headers
* @param int $option
*
* @return JsonResponse
*/
protected function formatArrayResponse($data, string $message = '', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
{
return response()->json($this->formatData($data, $message, $code), $code, $headers, $option);
}
/**
* @param JsonResource|array|null $data
* @param $message
* @param $code
*
* @return array
*/
protected function formatData($data, $message, &$code)
{
$originalCode = $code;
$code = (int) substr($code, 0, 3); // notice
if ($code >= 400 && $code <= 499) {// client error
$status = 'error';
} elseif ($code >= 500 && $code <= 599) {// service error
$status = 'fail';
} else {
$status = 'success';
}
return [
'status' => $status,
'code' => $originalCode,
'message' => $message,
'data' => $data ?: (object) $data,
];
}
/**
* Format paginated resource data.
*
* @param JsonResource $resource
* @param string $message
* @param int $code
* @param array $headers
* @param int $option
*
* @return HigherOrderTapProxy|mixed
*/
protected function formatPaginatedResourceResponse($resource, string $message = '', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
{
$paginated = $resource->resource->toArray();
$paginationInformation = [
'meta' => [
'pagination' => [
'total' => $paginated['total'] ?? null,
'count' => $paginated['to'] ?? null,
'per_page' => $paginated['per_page'] ?? null,
'current_page' => $paginated['current_page'] ?? null,
'total_pages' => $paginated['last_page'] ?? null,
'links' => [
'previous' => $paginated['prev'] ?? null,
'next' => $paginated['next_page_url'] ?? null,
],
],
],
];
$data = array_merge_recursive(['data' => $this->parseDataFrom($resource)], $paginationInformation);
return tap(
response()->json($this->formatData($data, $message, $code), $code, $headers, $option),
function ($response) use ($resource) {
$response->original = $resource->resource->map(
function ($item) {
return is_array($item) ? Arr::get($item, 'resource') : $item->resource;
}
);
$resource->withResponse(request(), $response);
}
);
}
/**
* Format JsonResource Data.
*
* @param JsonResource $resource
* @param string $message
* @param int $code
* @param array $headers
* @param int $option
*
* @return HigherOrderTapProxy|mixed
*/
protected function formatResourceResponse($resource, string $message = '', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
{
return tap(
response()->json($this->formatData($this->parseDataFrom($resource), $message, $code), $code, $headers, $option),
function ($response) use ($resource) {
$response->original = $resource->resource;
$resource->withResponse(request(), $response);
}
);
}
/**
* Parse data from JsonResource.
*
* @param JsonResource $data
*
* @return array
*/
protected function parseDataFrom(JsonResource $data)
{
return array_merge_recursive($data->resolve(request()), $data->with(request()), $data->additional);
}
}
在父类控制器中,引入 App/Http/Controllers
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use App\Support\Traits\ResponseTrait;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use ResponseTrait;
}
子控制器中使用
// 操作成功情况
$this->response->success($data,$message);
$this->response->success(new UserCollection($resource), '成功');// 返回 API Resouce Collection
$this->response->success(new UserResource($user), '成功');// 返回 API Resouce
$user = ["name"=>"nickname","email"=>"longjian.huang@foxmail.com"];
$this->response->success($user, '成功');// 返回普通数组
$this->response->created($data,$message);
$this->response->accepted($message);
$this->response->noContent($message);
// 操作失败或异常情况
$this->response->fail($message);
$this->response->errorNotFound();
$this->response->errorBadRequest();
$this->response->errorForbidden();
$this->response->errorInternal();
$this->response->errorUnauthorized();
$this->response->errorMethodNotAllowed();