中间件抛出异常返回500而非预期状态码,是因为laravel默认仅识别继承httpexception或含getstatuscode()方法的异常;普通exception子类需实现responsable接口、继承httpresponseexception或在handler::render()中手动处理。

中间件里 throw 新异常后为什么 500 而不是预期状态码
直接 throw new CustomException() 不生效,是因为 Laravel 的异常处理机制默认只识别继承自 HttpException 或带 getStatusCode() 方法的异常。普通 Exception 子类会被兜底为 500。
- 必须让自定义异常实现
Illuminate\Contracts\Support\Responsable接口,或继承Illuminate\Http\Exceptions\HttpResponseException,或至少提供getStatusCode()和getHeaders()方法 - 更稳妥的做法是继承
Illuminate\Http\Exceptions\HttpResponseException,它本身不触发全局异常处理器,而是直接返回响应 - 如果坚持用普通异常,需在
app/Exceptions/Handler.php的render()方法中手动匹配并返回对应响应
中间件中抛出异常时 request 生命周期已到哪一步
中间件在请求进入路由前执行,此时 request 对象已创建、路由尚未匹配,$next($request) 后才进入后续中间件或控制器。所以你在中间件里 throw,Laravel 还没走到路由解析那步,也就不会触发控制器层的 try/catch 或模型事件。
- 异常发生时,
$request->route()是null,别试图读取路由参数或命名 - 能安全访问的是
$request->headers、$request->query、$request->ip()等基础属性 - 不要在异常对象构造时依赖
app()或服务容器未就绪的单例(比如某些数据库连接)
如何让自定义异常自动带上 JSON 响应结构
Laravel 默认对 API 请求返回 JSON 异常,但前提是异常被 Handler 正确识别为“可响应”。光抛出异常不够,得让它能被格式化。
- 推荐做法:让异常类实现
Responsable接口,在toResponse($request)中返回response()->json([...], $this->status) - 避免重写
Handler::render()做全量判断——容易漏掉新异常类型,也违背单一职责 - 如果项目统一用 API 响应,可在异常基类里固定
Accept: application/json检查,但注意 Web 请求也会走同一逻辑,需加$request->expectsJson()判断
class ApiAuthException extends Exception implements Responsable
{
public function __construct(public int $status = 401)
{
parent::__construct('Unauthorized');
}
public function toResponse($request)
{
return response()->json([
'message' => $this->getMessage(),
'status' => $this->status,
], $this->status);
}
}
中间件抛异常后日志里看不到上下文怎么办
默认 Logger 只记录异常消息和堆栈,不包含当前请求的 URL、method、IP 或 header,排查时经常抓瞎。
- 在中间件里
throw前,先调用Log::debug('auth failed', [...])手动补全上下文 - 更彻底的方式:在
Handler::report()中检查异常是否来自中间件(比如用debug_backtrace()查是否有Middleware关键字),再注入请求信息 - 注意别把敏感 header(如
Authorization)直接打日志,用Str::mask()或白名单过滤
try/catch 捕获,其实它根本不会进控制器代码块。










