Laravel - 中间件
Laravel - 中间件
Section titled “Laravel - 中间件”Middleware(中间件)提供了一种方便的机制,用于检查和过滤进入应用的 HTTP 请求。例如,Laravel 内置了一个中间件,用于验证应用用户是否已认证。如果用户未认证,该中间件会将用户重定向到应用的登录界面。但是,如果用户已认证,中间件将允许请求进一步进入应用。
可以将中间件视为 HTTP 请求在到达应用之前必须通过的层。每一层都可以检查请求,甚至可以完全拒绝它。
中间件充当请求和响应之间的中介。它是一种过滤机制。例如,Laravel 内置的 Authenticate 中间件检查用户是否已登录。如果已登录,请求继续处理;否则,用户将被重定向到登录页面。
你可以使用 Artisan 命令生成新的中间件:
php artisan make:middleware <MiddlewareName>将 <MiddlewareName> 替换为你想要的中间件名称(例如,CheckUserAge)。新的中间件类将放置在 app/Http/Middleware 目录下。
示例:创建一个年龄中间件
Section titled “示例:创建一个年龄中间件”步骤 1:生成中间件。我们来创建一个 EnsureUserIsOldEnough 中间件:
php artisan make:middleware EnsureUserIsOldEnough步骤 2:运行命令后,Laravel 将输出一条消息,指示中间件已成功创建:
INFO Middleware [app/Http/Middleware/EnsureUserIsOldEnough.php] created successfully.步骤 3:app/Http/Middleware 目录下将创建 EnsureUserIsOldEnough.php 文件。它将包含一个 handle 方法。我们来修改它,检查请求中假设的 ‘age’ 参数是否大于或等于 18:
<?php
namespace App\Http\Middleware;
use Closure;use Illuminate\Http\Request;use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsOldEnough{ /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next * @return \Symfony\Component\HttpFoundation\Response */ public function handle(Request $request, Closure $next): Response { // 示例:检查 'age' 输入或用户的年龄属性 // 这是一个简化示例。在实际应用中,你会从数据库或 session 中获取用户年龄。 if ($request->input('age', 0) < 18) { // 重定向到不同页面或返回错误响应 return redirect('home')->with('error', 'You must be 18 or older to access this page.'); }
return $next($request); }}在使用中间件之前,必须在应用的 HTTP 内核中注册它。HTTP 内核位于 app/Http/Kernel.php。
有三种主要的注册中间件的方式:
- 全局中间件 (Global Middleware): 它们会运行在每个进入应用的 HTTP 请求上。在
app/Http/Kernel.php类的$middleware属性中注册它们。 - 中间件组 (Middleware Groups): 将多个中间件分组到一个键下,以便更轻松地分配给路由。Laravel 默认的
web和api组定义在$middlewareGroups属性中。 - 路由中间件 (Route Middleware / Aliases): 为中间件分配一个短别名 (alias),然后可以将其分配给特定路由。在
$middlewareAliases(或旧版本 Laravel 中的$routeMiddleware) 属性中注册它们。
注册全局中间件:
Section titled “注册全局中间件:”要让中间件在每次 HTTP 请求期间运行,请将其添加到 $middleware 属性中:
protected $middleware = [ // ... 其他全局中间件 \App\Http\Middleware\EnsureUserIsOldEnough::class, // 如果你想让它成为全局中间件];注册路由中间件(别名):
Section titled “注册路由中间件(别名):”对于仅适用于特定路由的中间件,请在 $middlewareAliases 属性中为其分配一个键。此键将用于在路由定义中引用该中间件。
protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, // ... 其他别名 'isOldEnough' => \App\Http\Middleware\EnsureUserIsOldEnough::class, // 我们的自定义别名];示例:将 EnsureUserIsOldEnough 注册为路由中间件
Section titled “示例:将 EnsureUserIsOldEnough 注册为路由中间件”我们来将 EnsureUserIsOldEnough 中间件注册到 app/Http/Kernel.php 中,并设置别名为 isOldEnough:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel{ // ... (其他属性,如 $middleware, $middlewareGroups)
protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, // ... 其他默认别名 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'isOldEnough' => \App\Http\Middleware\EnsureUserIsOldEnough::class, // 添加了我们的中间件别名 // ... 其他别名 ];}注册后,你可以在 routes/web.php 文件中将 isOldEnough 中间件分配给路由:
use App\Http\Controllers\ProfileController;
Route::get('/restricted-area', [ProfileController::class, 'show'])->middleware('isOldEnough');
// 你也可以将它应用到一个路由组:Route::middleware(['isOldEnough'])->group(function () { Route::get('/another-restricted-page', function () { return 'Welcome to another restricted page!'; });});中间件还可以接收额外的参数。例如,如果你的应用需要验证用户的角色才能允许访问某些操作,一个 CheckRole 中间件可以接收所需的角色作为参数。
要传递参数,请在路由定义中将参数附加到中间件名称后面,用冒号 : 分隔。多个参数用逗号分隔。这些参数将作为 $next 参数之后的参数传递给 handle 方法。
示例:创建并使用 CheckRole 中间件
Section titled “示例:创建并使用 CheckRole 中间件”步骤 1:创建 CheckRole 中间件:
php artisan make:middleware CheckRole步骤 2:修改 app/Http/Middleware/CheckRole.php 中的 handle 方法以接受一个角色参数:
<?php
namespace App\Http\Middleware;
use Closure;use Illuminate\Http\Request;use Symfony\Component\HttpFoundation\Response;
class CheckRole{ /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string $role 需要检查的角色。 * @return \Symfony\Component\HttpFoundation\Response */ public function handle(Request $request, Closure $next, string $role): Response { // 假设 $request->user()->hasRole($role) 是一个检查用户角色的方法 // 这是你实际角色检查逻辑的占位符。 if (! $request->user() || ! $request->user()->hasRole($role)) { // 为了演示,我们直接中止请求。在实际应用中,应重定向或返回 JSON 错误。 abort(403, 'Unauthorized action. Required role: ' . $role); }
// 对于本教程的原始输出风格: // echo "Required Role: ".$role."<br>";
return $next($request); }}步骤 3:在 app/Http/Kernel.php 中注册 CheckRole 中间件并为其设置别名,例如 role:
protected $middlewareAliases = [ // ... 其他别名 'role' => \App\Http\Middleware\CheckRole::class,];步骤 4:创建一个简单的控制器,例如 AdminController:
php artisan make:controller AdminController向 app/Http/Controllers/AdminController.php 添加一个方法:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdminController extends Controller{ public function dashboard() { return "Welcome to the Admin Dashboard!"; }}步骤 5:在 routes/web.php 中定义一个使用中间件并带有参数的路由:
// 在 routes/web.php 文件中use App\Http\Controllers\AdminController;
// 这假设你已设置认证并且用户模型有 hasRole() 方法。// 如果没有完整的认证设置,你可能需要模拟用户或调整中间件逻辑以进行测试。Route::get('/admin/dashboard', [AdminController::class, 'dashboard']) ->middleware(['auth', 'role:editor']); // 需要 'auth' 和 'role' 中间件,'role' 带参数 'editor'。步骤 6:要测试此功能,请访问 /admin/dashboard。如果认证用户拥有 ‘editor’ 角色,他们将看到仪表板。否则,他们将收到 403 错误(或你在 CheckRole 中实现的任何逻辑)。
常见陷阱:如果你的 CheckRole 中间件依赖于 $request->user(),请确保它在认证中间件 (auth) 之后运行。
有时,中间件可能需要在响应发送到浏览器之后执行一些工作。例如,会话中间件可能在响应发送后将会话数据写入存储。为了实现这一点,在你的中间件上定义一个 terminate 方法。
terminate 方法接收请求和响应。可终止的中间件应注册为全局中间件或包含在中间件组中。
示例:创建一个 LogResponseSent 终止中间件
Section titled “示例:创建一个 LogResponseSent 终止中间件”步骤 1:创建 LogResponseSent 中间件:
php artisan make:middleware LogResponseSent步骤 2:向 app/Http/Middleware/LogResponseSent.php 添加 handle 和 terminate 方法:
<?php
namespace App\Http\Middleware;
use Closure;use Illuminate\Http\Request;use Illuminate\Support\Facades\Log;use Symfony\Component\HttpFoundation\Response;
class LogResponseSent{ public function handle(Request $request, Closure $next): Response { // Log::info('LogResponseSent: 在将请求发送到下一个中间件/控制器之前处理请求。'); // 对于本教程的原始输出风格: // echo "Executing statements of handle method of LogResponseSent Middleware.<br>"; return $next($request); }
/** * 处理响应已发送到浏览器后的任务。 * * @param \Illuminate\Http\Request $request * @param \Symfony\Component\HttpFoundation\Response $response * @return void */ public function terminate(Request $request, Response $response): void { Log::info('LogResponseSent: 响应已发送到浏览器。', [ 'url' => $request->fullUrl(), 'status' => $response->getStatusCode() ]); // 对于本教程的原始输出风格: // echo "<br>Executing statements of terminate method of LogResponseSent Middleware."; }}步骤 3:将 LogResponseSent 注册为全局中间件(或注册到像 web 这样的组中)在 app/Http/Kernel.php 里:
protected $middleware = [ // ... 其他全局中间件 \App\Http\Middleware\LogResponseSent::class,];步骤 4:创建一个控制器,例如 HomeController:
php artisan make:controller HomeController添加一个索引方法:
<?php
namespace App\Http\Controllers;
class HomeController extends Controller{ public function index() { // echo "<br>HomeController: Processing request.<br>"; return "Welcome Home!"; }}步骤 5:在 routes/web.php 中定义一个路由:
// 在 routes/web.php 文件中use App\Http\Controllers\HomeController;
Route::get('/home-terminable', [HomeController::class, 'index']);步骤 6:当你访问 http://your-app.test/home-terminable 时,首先运行 handle 方法中的逻辑,然后是控制器,接着浏览器接收到 “Welcome Home!”,最后执行 terminate 方法中的逻辑,写入你的日志文件(例如,storage/logs/laravel.log)。
要查看原始教程风格的 echoed 输出,你需要确保你的 Web 服务器配置(如带 PHP-FPM 的 Nginx)允许输出缓冲,并且能够刷新,这样 terminate 中的 echo 才能可见。但是,日志记录是标准的用例。
有关更深入的信息,请参考官方 Laravel 关于中间件的文档:https://laravel.com/docs/middleware