不能直接 throw new Exception(),因其无法携带 HTTP 状态码、业务错误码、标准化 JSON 响应及可分类日志;应定义实现 Responsable 接口的 BusinessException 类,统一控制 statusCode、code 字段,并在 toResponse() 中返回标准 JSON 响应。

为什么不能直接 throw new Exception()?
在 Laravel 业务逻辑里随手写 throw new Exception('用户余额不足'),看似简单,但会丢失关键上下文:HTTP 状态码、响应格式、日志分类、前端可识别的错误码。Laravel 的异常处理机制默认把所有 Exception 当作 500 错误,无法区分「参数错」还是「业务规则拒绝」。
真正需要的是可预测、可捕获、可序列化的业务异常——比如支付失败要返回 402,库存不足要返回 409,且都带 code 字段供前端跳转或重试逻辑判断。
如何定义一个标准的业务异常类?
继承 Exception 不够,必须实现 Illuminate\Contracts\Support\Responsable 接口,才能被 Laravel 异常处理器自动转成 HTTP 响应。推荐放在 app/Exceptions/BusinessException.php:
namespace App\Exceptions;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class BusinessException extends \Exception implements Responsable
{
public function __construct(
string $message,
public int $statusCode = 400,
public int $code = 10001,
?\Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}
public function toResponse(Request $request): Response
{
return response()->json([
'code' => $this->code,
'message' => $this->message,
'data' => null,
], $this->statusCode);
}
}
关键点:
-
$statusCode控制 HTTP 状态,别硬写 500;400 类适合参数/校验失败,409 适合冲突(如并发下单),402 适合支付相关 -
$code是业务错误码,必须全局唯一,建议按模块分段(如 101xx 用户模块,102xx 订单模块) - 不要在
toResponse()里调用abort()或抛新异常,会绕过异常处理器
怎么在控制器或 Service 里正确抛出和捕获?
抛出很简单:throw new BusinessException('库存不足', 409, 10203);。但捕获要注意场景:
- 在控制器中主动 try/catch:适合需要降级逻辑(如库存不足时查备用仓),此时 catch 后应 return 正常响应,而不是再 throw
- 在 Service 层不建议 try/catch 业务异常——它本就是向上冒泡的信号,让 Controller 或中间件统一处理
- 全局捕获靠
app/Exceptions/Handler.php的render()方法,但 BusinessExeption 已实现Responsable,所以 render 里不用额外处理,除非你要统一加 trace_id 或屏蔽敏感字段
示例(Controller):
public function placeOrder(Request $request)
{
try {
$order = $this->orderService->create($request->all());
return response()->json(['order_id' => $order->id]);
} catch (BusinessException $e) {
// 仅当需特殊处理时才在这里 catch,否则让它自然冒泡
Log::warning('订单创建业务异常', ['code' => $e->code, 'msg' => $e->message]);
throw $e; // 继续交给异常处理器
}
}
容易忽略的兼容性坑
Laravel 10+ 默认启用了严格模式,如果自定义异常类没声明构造函数参数类型(如 string $message),PHP 8.2+ 会报 TypeError。还有几个高频雷:
- 忘记在
use块里引入Responsable接口,类能跑但toResponse()不生效,仍走默认 500 页面 - 在
toResponse()中返回非Response实例(比如直接 return array),Laravel 会静默 fallback 到 500 - 测试时用
expectException(BusinessException::class)没问题,但若测试 JSON 结构,得用assertJsonPath('code', 10203),不能只断状态码——因为不同 BusinessException 可能共用 400
最麻烦的是日志脱敏:BusinessException 的 message 可能含用户手机号或金额,别直接 Log::error($e),要用 Log::error('BusinessException', ['code' => $e->code]) 显式过滤。


















