Files
vibe_coding/.cursor/skills/api-scaffold/references/exception-handling.md
2026-03-05 21:27:11 +08:00

101 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API Scaffold — 分级异常体系
> 主流程见 SKILL.md本文档为异常分级与 AppExceptionHandler 完整实现。
## 异常分级
| 异常类 | 场景 | HTTP 状态码 | 日志级别 | 是否通知运维 |
|---|---|---|---|---|
| `BusinessException` | 可预期的业务错误 | 400/403/404/409/422 | warning | ❌ |
| `ValidationException` | 参数校验失败 | 422 | info | ❌ |
| `SystemException` | 不可预期的系统故障 | 500/502/503 | error | ✅ |
## BusinessException / SystemException 定义
```php
// 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
<?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; }
}
```
## 异常选择决策
```
抛出异常?
├─ 用户输入/请求导致的?
│ ├─ 参数格式错误 → ValidationExceptionFormRequest 自动抛出)
│ ├─ 资源不存在 → BusinessException(404)
│ ├─ 无权限 → BusinessException(403)
│ └─ 业务规则冲突 → BusinessException(409/422)
└─ 系统/环境导致的?
├─ 数据库连接失败 → SystemException(503)
├─ 第三方 API 超时 → SystemException(502)
└─ 未知异常 → AppExceptionHandler 兜底
```
## Service 使用示例
```php
// 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);
```