136 lines
4.6 KiB
PHP
136 lines
4.6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Aether\Exception;
|
||
|
||
use Aether\AetherValidator;
|
||
use Hyperf\Contract\StdoutLoggerInterface;
|
||
use Hyperf\Database\Model\ModelNotFoundException; // 引入模型未找到异常
|
||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||
use Hyperf\Validation\ValidationException;
|
||
use Psr\Http\Message\ResponseInterface;
|
||
use Throwable;
|
||
use Hyperf\Context\Context;
|
||
use function Hyperf\Support\env;
|
||
|
||
class AetherExceptionHandler extends ExceptionHandler
|
||
{
|
||
public function __construct(protected StdoutLoggerInterface $logger)
|
||
{
|
||
}
|
||
|
||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||
{
|
||
$requestId = Context::get('request_id', uniqid());
|
||
$result = $this->formatErrorResponse($throwable, $requestId);
|
||
|
||
// 记录错误日志(包含完整堆栈)
|
||
$this->logger->error(sprintf(
|
||
'Exception [%s] | RequestId: %s | Message: %s in %s:%d',
|
||
get_class($throwable),
|
||
$requestId,
|
||
$throwable->getMessage(),
|
||
$throwable->getFile(),
|
||
$throwable->getLine()
|
||
));
|
||
if (env('APP_ENV') === 'dev') {
|
||
$this->logger->error($throwable->getTraceAsString());
|
||
}
|
||
|
||
// 确保状态码合法(100-599)
|
||
$statusCode = $result['code'] ?? 500;
|
||
if ($statusCode < 100 || $statusCode >= 600) {
|
||
$statusCode = 500;
|
||
}
|
||
|
||
// 构建响应
|
||
return $response
|
||
->withHeader('Content-Type', 'application/json')
|
||
->withStatus($statusCode)
|
||
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||
}
|
||
|
||
private function formatErrorResponse(Throwable $throwable, string $requestId): array
|
||
{
|
||
// 模型未找到异常
|
||
if ($throwable instanceof ModelNotFoundException) {
|
||
return [
|
||
'code' => 404, // 资源不存在标准状态码
|
||
'message' => $throwable->getMessage() ?: '请求的资源不存在',
|
||
'data' => env('APP_ENV') === 'dev' ? [
|
||
'file' => $throwable->getFile(),
|
||
'line' => $throwable->getLine()
|
||
] : null,
|
||
'request_id' => $requestId,
|
||
'timestamp' => time()
|
||
];
|
||
}
|
||
|
||
// 自定义验证异常
|
||
if ($throwable instanceof ValidationFailedException) {
|
||
return $this->formatValidationError($throwable, $requestId);
|
||
}
|
||
|
||
// 原生验证异常
|
||
if ($throwable instanceof ValidationException) {
|
||
return $this->formatNativeValidationError($throwable, $requestId);
|
||
}
|
||
|
||
// 其他异常(如RuntimeException等)
|
||
return [
|
||
'code' => 600,
|
||
'message' => env('APP_ENV') === 'dev' ? $throwable->getMessage() : '服务暂时不可用',
|
||
'data' => env('APP_ENV') === 'dev' ? [
|
||
'file' => $throwable->getFile(),
|
||
'line' => $throwable->getLine(),
|
||
'trace' => explode("\n", $throwable->getTraceAsString())
|
||
] : null,
|
||
'request_id' => $requestId,
|
||
'timestamp' => time()
|
||
];
|
||
}
|
||
|
||
private function formatValidationError(ValidationFailedException $e, string $requestId): array
|
||
{
|
||
$validatorInstance = new class extends AetherValidator {
|
||
protected function scenes(): array { return []; }
|
||
};
|
||
|
||
return [
|
||
'code' => 422,
|
||
'message' => $e->getMessage(),
|
||
'data' => [
|
||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||
'scene' => $e->getScene(),
|
||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||
],
|
||
'request_id' => $requestId,
|
||
'timestamp' => time()
|
||
];
|
||
}
|
||
|
||
private function formatNativeValidationError(ValidationException $e, string $requestId): array
|
||
{
|
||
$validatorInstance = new class extends AetherValidator {
|
||
protected function scenes(): array { return []; }
|
||
};
|
||
|
||
return [
|
||
'code' => 422,
|
||
'message' => '参数验证失败',
|
||
'data' => [
|
||
'errors' => $validatorInstance->formatValidationErrors($e->validator),
|
||
'validated_data' => env('APP_ENV') === 'dev' ? $e->validator->getData() : null
|
||
],
|
||
'request_id' => $requestId,
|
||
'timestamp' => time()
|
||
];
|
||
}
|
||
|
||
public function isValid(Throwable $throwable): bool
|
||
{
|
||
return true;
|
||
}
|
||
} |