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

3.7 KiB
Raw Permalink Blame History

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; }
}

异常选择决策

抛出异常?
├─ 用户输入/请求导致的?
│  ├─ 参数格式错误 → ValidationExceptionFormRequest 自动抛出)
│  ├─ 资源不存在 → 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);