3.7 KiB
3.7 KiB
API Scaffold — 分级异常体系
主流程见 SKILL.md,本文档为异常分级与 AppExceptionHandler 完整实现。
异常分级
| 异常类 | 场景 | HTTP 状态码 | 日志级别 | 是否通知运维 |
|---|---|---|---|---|
BusinessException |
可预期的业务错误 | 400/403/404/409/422 | warning | ❌ |
ValidationException |
参数校验失败 | 422 | info | ❌ |
SystemException |
不可预期的系统故障 | 500/502/503 | error | ✅ |
BusinessException / SystemException 定义
// app/Exception/BusinessException.php
class BusinessException extends ServerException
{
public function __construct(int $code = 400, string $message = 'Business error', public readonly bool $isOperational = true)
{
parent::__construct($message, $code);
}
}
// app/Exception/SystemException.php
class SystemException extends ServerException
{
public function __construct(string $message = 'System error', int $code = 500, public readonly bool $isOperational = false, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
AppExceptionHandler
<?php
namespace App\Exception\Handler;
use App\Exception\BusinessException;
use App\Exception\SystemException;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Validation\ValidationException;
class AppExceptionHandler extends ExceptionHandler
{
public function handle(\Throwable $throwable, ResponseInterface $response): ResponseInterface
{
$this->stopPropagation();
$body = match (true) {
$throwable instanceof ValidationException => ['code' => 422, 'message' => 'Validation failed', 'data' => $throwable->validator->errors()->toArray()],
$throwable instanceof BusinessException => ['code' => $throwable->getCode(), 'message' => $throwable->getMessage(), 'data' => null],
$throwable instanceof SystemException => $this->handleSystemException($throwable),
default => $this->handleUnexpected($throwable),
};
$statusCode = ($body['code'] >= 100 && $body['code'] < 600) ? $body['code'] : 500;
return $response->withStatus($statusCode)->withHeader('Content-Type', 'application/json')
->withBody(new SwooleStream(json_encode($body, JSON_UNESCAPED_UNICODE)));
}
private function handleSystemException(SystemException $e): array {
$this->logger->error('System exception', ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
return ['code' => 500, 'message' => 'Internal server error', 'data' => null];
}
private function handleUnexpected(\Throwable $e): array {
$this->logger->critical('Unexpected exception', ['class' => get_class($e), 'message' => $e->getMessage()]);
return ['code' => 500, 'message' => 'Internal server error', 'data' => null];
}
public function isValid(\Throwable $throwable): bool { return true; }
}
异常选择决策
抛出异常?
├─ 用户输入/请求导致的?
│ ├─ 参数格式错误 → ValidationException(FormRequest 自动抛出)
│ ├─ 资源不存在 → BusinessException(404)
│ ├─ 无权限 → BusinessException(403)
│ └─ 业务规则冲突 → BusinessException(409/422)
└─ 系统/环境导致的?
├─ 数据库连接失败 → SystemException(503)
├─ 第三方 API 超时 → SystemException(502)
└─ 未知异常 → AppExceptionHandler 兜底
Service 使用示例
// Predictable: resource not found
throw new BusinessException(404, 'Order not found');
// Unpredictable: external system failure
throw new SystemException(message: 'ERP sync failed: connection timeout', previous: $e);