|
@@ -4,11 +4,13 @@ namespace App\Exceptions;
|
|
|
|
|
|
use Exception;
|
|
|
use Illuminate\Auth\AuthenticationException;
|
|
|
+use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
|
|
use Illuminate\Session\TokenMismatchException;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Validation\ValidationException;
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
+use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
use Throwable;
|
|
|
|
|
@@ -25,82 +27,112 @@ class Handler extends ExceptionHandler
|
|
|
'password_confirmation',
|
|
|
];
|
|
|
|
|
|
+ /**
|
|
|
+ * 统一的响应格式
|
|
|
+ */
|
|
|
+ private function formatJsonResponse(int $code, string $message, int $httpCode = 200, array $data = null): \Illuminate\Http\JsonResponse
|
|
|
+ {
|
|
|
+ return response()->json([
|
|
|
+ 'code' => $code,
|
|
|
+ 'message' => $message,
|
|
|
+ 'data' => $data,
|
|
|
+ ], $httpCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统一的日志记录
|
|
|
+ */
|
|
|
+ private function logException(Throwable $e, string $level = 'error', array $extra = []): void
|
|
|
+ {
|
|
|
+ $context = [
|
|
|
+ 'exception' => get_class($e),
|
|
|
+ 'file' => $e->getFile(),
|
|
|
+ 'line' => $e->getLine(),
|
|
|
+ 'trace' => $e->getTraceAsString(),
|
|
|
+ 'url' => request()->fullUrl(),
|
|
|
+ 'method' => request()->method(),
|
|
|
+ 'ip' => request()->ip(),
|
|
|
+ 'user_agent' => request()->userAgent(),
|
|
|
+ 'user_id' => auth()->id(),
|
|
|
+ ...$extra
|
|
|
+ ];
|
|
|
+
|
|
|
+ Log::{$level}($e->getMessage(), $context);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Register the exception handling callbacks for the application.
|
|
|
*/
|
|
|
public function register(): void
|
|
|
{
|
|
|
+ // 记录所有异常
|
|
|
$this->reportable(function (Throwable $e) {
|
|
|
- $context = [
|
|
|
- 'exception' => get_class($e),
|
|
|
- 'file' => $e->getFile(),
|
|
|
- 'line' => $e->getLine(),
|
|
|
- 'trace' => $e->getTraceAsString(),
|
|
|
- 'url' => request()->fullUrl(),
|
|
|
- 'method' => request()->method(),
|
|
|
- 'ip' => request()->ip(),
|
|
|
- 'user_agent' => request()->userAgent(),
|
|
|
- ];
|
|
|
-
|
|
|
- if ($e instanceof NotFoundHttpException) {
|
|
|
- Log::error('Route not found: '.request()->fullUrl(), $context);
|
|
|
+ if ($e instanceof ValidationException) {
|
|
|
+ $this->logException($e, 'info');
|
|
|
} else {
|
|
|
- Log::error($e->getMessage(), $context);
|
|
|
+ $this->logException($e);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ // 404异常
|
|
|
$this->renderable(function (NotFoundHttpException $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => 404,
|
|
|
- 'msg' => 'Route not found: '.request()->fullUrl(),
|
|
|
- ], Response::HTTP_NOT_FOUND);
|
|
|
+ return $this->formatJsonResponse(404, '请求的资源不存在', Response::HTTP_NOT_FOUND);
|
|
|
});
|
|
|
|
|
|
+ // 模型未找到异常
|
|
|
+ $this->renderable(function (ModelNotFoundException $e) {
|
|
|
+ return $this->formatJsonResponse(404, '请求的数据不存在', Response::HTTP_NOT_FOUND);
|
|
|
+ });
|
|
|
+
|
|
|
+ // CSRF Token异常
|
|
|
$this->renderable(function (TokenMismatchException $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => $e->getCode() ?: 419,
|
|
|
- 'msg' => $e->getMessage(),
|
|
|
- ], 419);
|
|
|
+ return $this->formatJsonResponse(419, 'CSRF Token 已过期,请刷新页面重试', 419);
|
|
|
});
|
|
|
|
|
|
+ // 认证异常
|
|
|
$this->renderable(function (AuthenticationException $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => $e->getCode() ?: 401,
|
|
|
- 'msg' => '登录超时!',
|
|
|
- ], Response::HTTP_UNAUTHORIZED);
|
|
|
+ return $this->formatJsonResponse(401, '请先登录', Response::HTTP_UNAUTHORIZED);
|
|
|
});
|
|
|
|
|
|
+ // 验证异常
|
|
|
$this->renderable(function (ValidationException $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => $e->getCode() ?: 422,
|
|
|
- 'msg' => $e->getMessage(),
|
|
|
- ], Response::HTTP_UNPROCESSABLE_ENTITY);
|
|
|
+ return $this->formatJsonResponse(422, $e->validator->errors()->first(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
|
|
});
|
|
|
|
|
|
+ // HTTP异常
|
|
|
+ $this->renderable(function (HttpException $e) {
|
|
|
+ return $this->formatJsonResponse($e->getStatusCode(), $e->getMessage(), $e->getStatusCode());
|
|
|
+ });
|
|
|
+
|
|
|
+ // 业务异常
|
|
|
$this->renderable(function (ApiException $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => $e->getCode() ?: 200,
|
|
|
- 'msg' => $e->getMessage(),
|
|
|
- ], 200);
|
|
|
+ return $this->formatJsonResponse($e->getCode() ?: 400, $e->getMessage());
|
|
|
});
|
|
|
|
|
|
+ // 其他所有异常
|
|
|
$this->renderable(function (Throwable $e) {
|
|
|
- return response()->json([
|
|
|
- 'code' => $e->getCode() ?: 500,
|
|
|
- 'msg' => $e->getMessage(),
|
|
|
- ], Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
+ if (config('app.debug')) {
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ return $this->formatJsonResponse(500, '服务器内部错误', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 渲染异常,所有异常都返回 统一的JSON 格式
|
|
|
+ * @param mixed $request
|
|
|
+ * @param \Throwable $exception
|
|
|
+ * @return mixed|Response|\Illuminate\Http\JsonResponse
|
|
|
+ */
|
|
|
public function render($request, Throwable $exception)
|
|
|
{
|
|
|
if ($request->expectsJson()) {
|
|
|
- if ($exception instanceof HttpException) {
|
|
|
+ if ($exception instanceof Exception) {
|
|
|
return response()->json([
|
|
|
- 'code' => $exception->getStatusCode(),
|
|
|
+ 'code' => $exception->getCode() ?: 500,
|
|
|
'message' => $exception->getMessage(),
|
|
|
'data' => null,
|
|
|
- ], $exception->getStatusCode());
|
|
|
+ ], status: Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
}
|
|
|
}
|
|
|
|