Laravel 13 中间件进阶指南 中间件是 Laravel 请求处理流程中的核心组件,提供了强大的请求过滤和响应处理能力。本文将深入探讨 Laravel 13 中间件的高级用法。
中间件基础回顾 创建中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class CheckUserRole { public function handle (Request $request , Closure $next , string $role ): Response { if (! $request ->user () || ! $request ->user ()->hasRole ($role )) { return response ()->json (['message' => 'Unauthorized' ], 403 ); } return $next ($request ); } }
注册中间件 1 2 3 4 5 6 7 ->withMiddleware (function (Middleware $middleware ) { $middleware ->alias ([ 'role' => \App\Http\Middleware\CheckUserRole ::class , 'throttle' => \App\Http\Middleware\ThrottleRequests ::class , ]); })
中间件参数 多参数中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class CheckPermission { public function handle ( Request $request , Closure $next , string $permission , string $guard = null ): Response { $guard = $guard ?? config ('auth.defaults.guard' ); if (! $request ->user ($guard )?->can ($permission )) { abort (403 , "Missing permission: {$permission} " ); } return $next ($request ); } }
路由中使用参数 1 2 3 4 Route ::middleware ('permission:edit-posts,admin' )->group (function () { Route ::put ('/posts/{post}' , [PostController ::class , 'update' ]); Route ::delete ('/posts/{post}' , [PostController ::class , 'destroy' ]); });
可终止中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;use App \Services \AnalyticsService ;class LogRequestDuration { public function __construct ( private AnalyticsService $analytics ) {} public function handle (Request $request , Closure $next ): Response { $request ->attributes->set ('start_time' , microtime (true )); return $next ($request ); } public function terminate (Request $request , Response $response ): void { $duration = microtime (true ) - $request ->attributes->get ('start_time' ); $this ->analytics->record ([ 'path' => $request ->path (), 'method' => $request ->method (), 'duration_ms' => round ($duration * 1000 , 2 ), 'status' => $response ->getStatusCode (), ]); } }
中间件优先级 定义优先级顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ->withMiddleware (function (Middleware $middleware ) { $middleware ->priority ([ \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests ::class , \Illuminate\Cookie\Middleware\EncryptCookies ::class , \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse ::class , \Illuminate\Session\Middleware\StartSession ::class , \Illuminate\View\Middleware\ShareErrorsFromSession ::class , \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests ::class , \Illuminate\Routing\Middleware\ThrottleRequests ::class , \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis ::class , \Illuminate\Contracts\Session\Middleware\AuthenticatesSessions ::class , \Illuminate\Routing\Middleware\SubstituteBindings ::class , \Illuminate\Auth\Middleware\Authorize ::class , \App\Http\Middleware\CheckUserRole ::class , ]); })
中间件组 创建中间件组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ->withMiddleware (function (Middleware $middleware ) { $middleware ->group ('api' , [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful ::class , \Illuminate\Routing\Middleware\ThrottleRequests ::class .':api' , \Illuminate\Routing\Middleware\SubstituteBindings ::class , ]); $middleware ->group ('web' , [ \Illuminate\Cookie\Middleware\EncryptCookies ::class , \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse ::class , \Illuminate\Session\Middleware\StartSession ::class , \Illuminate\View\Middleware\ShareErrorsFromSession ::class , \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken ::class , \Illuminate\Routing\Middleware\SubstituteBindings ::class , ]); })
全局中间件 添加全局中间件 1 2 3 4 5 6 7 8 ->withMiddleware (function (Middleware $middleware ) { $middleware ->append (\App\Http\Middleware\ForceHttps ::class ); $middleware ->prepend (\App\Http\Middleware\CheckMaintenanceMode ::class ); $middleware ->remove (\Illuminate\Http\Middleware\TrustProxies ::class ); })
全局中间件示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class ForceHttps { public function handle (Request $request , Closure $next ): Response { if (! $request ->secure () && app ()->environment ('production' )) { return redirect ()->secure ($request ->getRequestUri ()); } return $next ($request ); } }
排除中间件 路由级别排除 1 2 3 4 5 6 7 8 9 10 Route ::middleware (['auth' , 'verified' ])->group (function () { Route ::get ('/profile' , [ProfileController ::class , 'show' ]) ->withoutMiddleware (Verified ::class ); Route ::get ('/settings' , [SettingsController ::class , 'index' ]); }); Route ::withoutMiddleware ([Authenticate ::class ])->group (function () { Route ::get ('/public' , [PublicController ::class , 'index' ]); });
条件中间件 基于条件的中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class ConditionalCache { public function handle (Request $request , Closure $next , int $minutes = 60 ): Response { $response = $next ($request ); if ($this ->shouldCache ($request , $response )) { $response ->setPublic (); $response ->setMaxAge ($minutes * 60 ); $response ->setEtag (md5 ($response ->getContent ())); } return $response ; } private function shouldCache (Request $request , Response $response ): bool { return $request ->isMethod ('GET' ) && $response ->getStatusCode () === 200 && ! $request ->user () && ! $request ->ajax (); } }
请求修改中间件 添加请求数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class AddRequestContext { public function handle (Request $request , Closure $next ): Response { $request ->merge ([ '_request_id' => (string ) Str ::uuid (), '_timestamp' => now ()->toIso8601String (), '_ip' => $request ->ip (), '_user_agent' => $request ->userAgent (), ]); $request ->attributes->set ('request_id' , Str ::uuid ()->toString ()); return $next ($request ); } }
响应修改中间件 添加响应头 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;class AddSecurityHeaders { public function handle (Request $request , Closure $next ): Response { $response = $next ($request ); $response ->headers->set ('X-Content-Type-Options' , 'nosniff' ); $response ->headers->set ('X-Frame-Options' , 'DENY' ); $response ->headers->set ('X-XSS-Protection' , '1; mode=block' ); $response ->headers->set ('Referrer-Policy' , 'strict-origin-when-cross-origin' ); $response ->headers->set ('Permissions-Policy' , 'geolocation=(), microphone=()' ); if ($request ->isSecure ()) { $response ->headers->set ( 'Strict-Transport-Security' , 'max-age=31536000; includeSubDomains' ); } return $response ; } }
速率限制中间件 自定义速率限制器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 use Illuminate \Cache \RateLimiting \Limit ;use Illuminate \Http \Request ;use Illuminate \Support \Facades \RateLimiter ;public function boot ( ): void { RateLimiter ::for ('api' , function (Request $request ) { return Limit ::perMinute (60 )->by ($request ->user ()?->id ?: $request ->ip ()); }); RateLimiter ::for ('uploads' , function (Request $request ) { return $request ->user () ? Limit ::perMinute (10 )->by ($request ->user ()->id) : Limit ::perMinute (2 )->by ($request ->ip ()); }); RateLimiter ::for ('global' , function (Request $request ) { return Limit ::perMinute (1000 ) ->response (function () { return response ('Too many requests' , 429 ); }); }); }
动态速率限制 1 2 3 4 5 6 7 8 9 10 11 12 13 RateLimiter ::for ('dynamic' , function (Request $request ) { $user = $request ->user (); if ($user ?->isPremium ()) { return Limit ::none (); } if ($user ?->isVerified ()) { return Limit ::perMinute (100 )->by ($user ->id); } return Limit ::perMinute (30 )->by ($request ->ip ()); });
CORS 中间件 配置 CORS 1 2 3 4 5 6 7 8 9 10 11 return [ 'paths' => ['api/*' , 'sanctum/csrf-cookie' ], 'allowed_methods' => ['*' ], 'allowed_origins' => ['https://example.com' , 'https://app.example.com' ], 'allowed_origins_patterns' => ['/https?:\/\/([a-z0-9-]+\.)?example\.com/' ], 'allowed_headers' => ['*' ], 'exposed_headers' => ['X-Total-Count' , 'X-Page-Count' ], 'max_age' => 86400 , 'supports_credentials' => true , ];
中间件测试 测试中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php namespace Tests \Feature \Middleware ;use Tests \TestCase ;use App \Models \User ;use Illuminate \Http \Request ;use App \Http \Middleware \CheckUserRole ;use Symfony \Component \HttpFoundation \Response ;class CheckUserRoleTest extends TestCase { private CheckUserRole $middleware ; protected function setUp ( ): void { parent ::setUp (); $this ->middleware = new CheckUserRole (); } public function test_allows_user_with_correct_role ( ): void { $user = User ::factory ()->create (['role' => 'admin' ]); $request = Request ::create ('/admin' , 'GET' ); $request ->setUserResolver (fn () => $user ); $response = $this ->middleware->handle ($request , fn () => new Response (), 'admin' ); $this ->assertEquals (200 , $response ->getStatusCode ()); } public function test_denies_user_without_role ( ): void { $user = User ::factory ()->create (['role' => 'user' ]); $request = Request ::create ('/admin' , 'GET' ); $request ->setUserResolver (fn () => $user ); $response = $this ->middleware->handle ($request , fn () => new Response (), 'admin' ); $this ->assertEquals (403 , $response ->getStatusCode ()); } }
最佳实践 1. 单一职责 1 2 3 4 5 6 7 class Authenticate { }class Authorize { }class ThrottleRequests { }class AuthAndThrottleAndLog { }
2. 使用依赖注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use App \Services \GeoLocationService ;class RestrictByCountry { public function __construct ( private GeoLocationService $geoService ) {} public function handle (Request $request , Closure $next , string ...$countries ): Response { $country = $this ->geoService->getCountryCode ($request ->ip ()); if (! in_array ($country , $countries )) { abort (403 , 'This content is not available in your region.' ); } return $next ($request ); } }
3. 合理使用终止中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php namespace App \Http \Middleware ;use Closure ;use Illuminate \Http \Request ;use Symfony \Component \HttpFoundation \Response ;use Illuminate \Support \Facades \Log ;class RequestLogger { public function handle (Request $request , Closure $next ): Response { return $next ($request ); } public function terminate (Request $request , Response $response ): void { Log ::channel ('requests' )->info ('Request completed' , [ 'method' => $request ->method (), 'url' => $request ->fullUrl (), 'status' => $response ->getStatusCode (), 'duration' => defined ('LARAVEL_START' ) ? round ((microtime (true ) - LARAVEL_START) * 1000 , 2 ) : null , ]); } }
总结 Laravel 13 的中间件系统提供了强大而灵活的请求处理能力。通过合理使用中间件参数、可终止中间件、中间件组和优先级控制,可以构建出安全、高效、可维护的应用程序。记住保持中间件的单一职责原则,充分利用依赖注入,并在适当的时候使用终止中间件来处理耗时操作。