Handler.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <?php
  2. namespace App\Exceptions;
  3. use Exception;
  4. use Throwable;
  5. use Illuminate\Support\Facades\Log;
  6. use Illuminate\Auth\AuthenticationException;
  7. use Illuminate\Session\TokenMismatchException;
  8. use Illuminate\Validation\ValidationException;
  9. use Symfony\Component\HttpFoundation\Response;
  10. use Illuminate\Database\Eloquent\ModelNotFoundException;
  11. use Symfony\Component\HttpKernel\Exception\HttpException;
  12. use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
  13. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  14. class Handler extends ExceptionHandler
  15. {
  16. /**
  17. * The list of the inputs that are never flashed to the session on validation exceptions.
  18. *
  19. * @var array<int, string>
  20. */
  21. protected $dontFlash = [
  22. 'current_password',
  23. 'password',
  24. 'password_confirmation',
  25. ];
  26. /**
  27. * 统一的响应格式
  28. */
  29. private function formatJsonResponse(int $code, string $message, int $httpCode = 200, array $data = null): \Illuminate\Http\JsonResponse
  30. {
  31. return response()->json([
  32. 'code' => $code,
  33. 'message' => $message,
  34. 'data' => $data,
  35. ], $httpCode);
  36. }
  37. /**
  38. * 统一的日志记录
  39. */
  40. private function logException(Throwable $e, string $level = 'error', array $extra = []): void
  41. {
  42. $context = [
  43. 'exception' => get_class($e),
  44. 'file' => $e->getFile(),
  45. 'line' => $e->getLine(),
  46. 'trace' => $e->getTraceAsString(),
  47. 'url' => request()->fullUrl(),
  48. 'method' => request()->method(),
  49. 'ip' => request()->ip(),
  50. 'user_agent' => request()->userAgent(),
  51. 'user_id' => auth()->id(),
  52. ...$extra
  53. ];
  54. Log::{$level}($e->getMessage(), $context);
  55. }
  56. /**
  57. * Register the exception handling callbacks for the application.
  58. */
  59. public function register(): void
  60. {
  61. // 记录所有异常
  62. $this->reportable(function (Throwable $e) {
  63. if ($e instanceof ValidationException) {
  64. $this->logException($e, 'info');
  65. } else {
  66. $this->logException($e);
  67. }
  68. });
  69. // 404异常
  70. $this->renderable(function (NotFoundHttpException $e) {
  71. return $this->formatJsonResponse(404, '请求的资源不存在', Response::HTTP_NOT_FOUND);
  72. });
  73. // 模型未找到异常
  74. $this->renderable(function (ModelNotFoundException $e) {
  75. return $this->formatJsonResponse(404, '请求的数据不存在', Response::HTTP_NOT_FOUND);
  76. });
  77. // CSRF Token异常
  78. $this->renderable(function (TokenMismatchException $e) {
  79. return $this->formatJsonResponse(419, 'CSRF Token 已过期,请刷新页面重试', 419);
  80. });
  81. // 认证异常
  82. $this->renderable(function (AuthenticationException $e) {
  83. return $this->formatJsonResponse(401, '请先登录', Response::HTTP_UNAUTHORIZED);
  84. });
  85. // 验证异常
  86. $this->renderable(function (ValidationException $e) {
  87. return $this->formatJsonResponse(Response::HTTP_UNPROCESSABLE_ENTITY, $e->validator->errors()->first(), 200);
  88. });
  89. // HTTP异常
  90. $this->renderable(function (HttpException $e) {
  91. return $this->formatJsonResponse($e->getStatusCode(), $e->getMessage(), 200);
  92. });
  93. // 业务异常
  94. $this->renderable(function (ApiException $e) {
  95. return $this->formatJsonResponse($e->getCode() ?: 400, $e->getMessage());
  96. });
  97. // 其他所有异常
  98. $this->renderable(function (Throwable $e) {
  99. if (config('app.debug')) {
  100. throw $e;
  101. }
  102. return $this->formatJsonResponse(500, '服务器内部错误', Response::HTTP_INTERNAL_SERVER_ERROR);
  103. });
  104. }
  105. /**
  106. * 渲染异常,所有异常都返回 统一的JSON 格式
  107. * @param mixed $request
  108. * @param \Throwable $exception
  109. * @return mixed|Response|\Illuminate\Http\JsonResponse
  110. */
  111. public function render($request, Throwable $exception)
  112. {
  113. if ($request->expectsJson()) {
  114. if ($exception instanceof Exception) {
  115. return response()->json([
  116. 'code' => $exception->getCode() ?: 500,
  117. 'message' => $exception->getMessage(),
  118. 'data' => null,
  119. ], status: Response::HTTP_INTERNAL_SERVER_ERROR);
  120. }
  121. }
  122. return parent::render($request, $exception);
  123. }
  124. }