3.3 中间件管道与自定义门卫


3.3 中间件管道与自定义门卫

本节摘要:中间件是流水线上的门卫:请求进来前做检查,响应出去后做加工,任何一层不放手请求就走不到控制器。本节先讲清中间件的两个挂载层(全局与路由组)和执行顺序的确定规则,再带你亲手写一个"仅限施工时段访问"的自定义门卫,最后给出排障时验证中间件是否生效的三种手段。

接上节:安检是进车间的最后一道手续

路由匹配完成、参数绑定完成,请求还差一道手续才能见工头——过门卫。上一章你已经见过中间件管道的洋葱模型:前置检查、后置加工、不放行即拦截。本节把这三件事落到能跑的代码上。

两个挂载层:全局与路由组

中间件挂在哪里,决定了它管多宽:

  • 全局中间件:每个请求都过。Laravel 11 里在 bootstrap/app.php 的 withMiddleware 中追加,适合维护模式检查、请求日志这类真正全民都要的工序。
  • 路由中间件:挂在特定路由或分组上。auth、verified、throttle 这些框架自带的门卫都属此类,用别名引用,别名到类名的映射也配置在 withMiddleware 里。
<?php // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { // 给已有的 web 组追加门卫 $middleware->web(append: [ \App\Http\Middleware\LogSlowRequest::class, ]); // 注册别名,路由侧就能用 'shift.guard' 引用 $middleware->alias([ 'shift.guard' => \App\Http\Middleware\EnsureShiftOpen::class, ]); })
<?php // routes/web.php:别名挂到分组上 Route::middleware('shift.guard')->group(function () { Route::post('/orders', [OrderController::class, 'store']); Route::patch('/orders/{order}', [OrderController::class, 'update']); });

执行顺序:谁先谁后由什么定

同一请求上有多层门卫时,顺序按三档确定:全局层最先(按配置里追加的先后),然后是路由组由外到内,最后到控制器门前。若某一档内部需要强排序(比如"限流必须先于鉴权"),可以在 withMiddleware 里用 priority 声明。判断顺序的实操技巧是:在两个中间件里各写一行日志,发一个请求看落盘先后,比背规则可靠。

亲手养一个门卫

需求来了:接单窗口只在施工时段开放,其余时间一律婉拒。三步走:

<?php // 第一步 生成门卫骨架 // php artisan make:middleware EnsureShiftOpen namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class EnsureShiftOpen { public function handle(Request $request, Closure $next, string $openAt = '09', string $closeAt = '19'): Response { $hour = (int) now()->format('G'); // 前置检查:不在施工时段,直接婉拒,请求不再深入 if ($hour < (int) $openAt || $hour >= (int) $closeAt) { return response()->json([ 'message' => "接单窗口 {$openAt} 点至 {$closeAt} 点开放", ], 403); } // 放行:把请求递给下一层 return $next($request); } }
<?php // 第二步(别名已在上一段注册) 第三步 带参数挂载 Route::middleware('shift.guard:08,20')->group(function () { Route::post('/orders', [OrderController::class, 'store']); });

注意 handle 签名里的 openAt 与 closeAt:路由侧冒号后传的参数会依次喂进来,一个门卫因此可以在不同路由上用不同班次。后置加工的写法同样值得记一笔——把活放在 next(request) 返回值之后:

<?php public function handle(Request $request, Closure $next): Response { $start = microtime(true); $response = $next($request); // 先放行 // 后置加工:响应已生成,此时统计全程耗时 $cost = microtime(true) - $start; if ($cost > 1.0) { logger()->warning('慢请求', ['uri' => $request->path(), '秒' => round($cost, 2)]); } return $response; }

图 3-3:门卫班次与拦截路线

图 3-3:门卫班次与拦截路线

验证门卫真在岗的三种手段

门卫写了没生效是最常见的悬案,三种查法按成本从低到高:跑 php artisan route:list --middleware 看目标路由到底挂了哪些门卫;临时在门卫里 logger() 一行,请求一次看日志;写一条 Feature 测试模拟非施工时段发请求断言 403(第九章会展开)。顺带提醒:改了别名或全局中间件配置后记得 config:clear 与 route:clear,别让缓存骗你。

💡 关键直觉:门卫只做判断不干活。一旦发现自己在中间件里写业务查询和复杂分支,多半是该做成控制器前置逻辑或第七章的授权策略了。

本节要点回顾

  • 两层挂载:全局管全民工序,路由组管区域门禁,别名让路由侧引用简洁。
  • 顺序三档:全局、组由外到内、按需 priority 强排序,拿不准就打日志验证。
  • 门卫三路线:放行走 next、拦截即返回、后置加工写在 next 之后。
  • 班次参数:冒号传参喂进 handle,一个门卫多处复用。

到这里,请求已经过了窗口、绑了参数、过了安检,正式到了工头手上。下一章控制器派工:工头怎么接单、怎么开质检单、怎么交付。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U