跳转至内容

路由

基础路由

最基本的 Laravel 路由仅接受一个 URI 和一个闭包,提供了一种非常简单且直观的方法来定义路由和行为,而无需复杂的路由配置文件。

1use Illuminate\Support\Facades\Route;
2 
3Route::get('/greeting', function () {
4 return 'Hello World';
5});

默认路由文件

所有的 Laravel 路由都在 routes 目录下的路由文件中定义。这些文件由 Laravel 使用应用程序 bootstrap/app.php 文件中指定的配置自动加载。routes/web.php 文件用于定义 Web 界面的路由。这些路由会被分配到 web 中间件组,该组提供了诸如会话状态和 CSRF 保护等功能。

对于大多数应用程序,您可以从在 routes/web.php 文件中定义路由开始。在 routes/web.php 中定义的路由可以通过在浏览器中输入定义的 URL 来访问。例如,您可以通过在浏览器中导航至 http://example.com/user 来访问以下路由。

1use App\Http\Controllers\UserController;
2 
3Route::get('/user', [UserController::class, 'index']);

API 路由

如果您的应用程序还将提供无状态 API,您可以使用 install:api Artisan 命令启用 API 路由。

1php artisan install:api

install:api 命令会安装 Laravel Sanctum,它提供了一种强大且简单的 API 令牌身份验证守卫,可用于验证第三方 API 用户、SPA 或移动应用程序。此外,install:api 命令还会创建 routes/api.php 文件。

1Route::get('/user', function (Request $request) {
2 return $request->user();
3})->middleware('auth:sanctum');

当然,您可以随意在需要公开访问的路由上省略 auth:sanctum 中间件。

routes/api.php 中的路由是无状态的,并被分配给 api 中间件组。此外,/api URI 前缀会自动应用于这些路由,因此您无需手动为文件中的每个路由添加前缀。您可以通过修改应用程序的 bootstrap/app.php 文件来更改此前缀。

1->withRouting(
2 api: __DIR__.'/../routes/api.php',
3 apiPrefix: 'api/admin',
4 // ...
5)

可用的路由器方法

路由器允许您注册响应任何 HTTP 动词的路由。

1Route::get($uri, $callback);
2Route::post($uri, $callback);
3Route::put($uri, $callback);
4Route::patch($uri, $callback);
5Route::delete($uri, $callback);
6Route::options($uri, $callback);

有时您可能需要注册一个响应多种 HTTP 动词的路由。您可以使用 match 方法来实现。或者,您甚至可以使用 any 方法注册一个响应所有 HTTP 动词的路由。

1Route::match(['get', 'post'], '/', function () {
2 // ...
3});
4 
5Route::any('/', function () {
6 // ...
7});

在定义多个共享相同 URI 的路由时,使用 getpostputpatchdeleteoptions 方法的路由应定义在使用 anymatchredirect 方法的路由之前。这可以确保传入的请求与正确的路由匹配。

依赖注入

您可以在路由的回调签名中对路由所需的任何依赖进行类型提示。声明的依赖项将由 Laravel 服务容器 自动解析并注入到回调中。例如,您可以对 Illuminate\Http\Request 类进行类型提示,以将当前的 HTTP 请求自动注入到您的路由回调中。

1use Illuminate\Http\Request;
2 
3Route::get('/users', function (Request $request) {
4 // ...
5});

CSRF 保护

请记住,在 web 路由文件中定义的指向 POSTPUTPATCHDELETE 路由的任何 HTML 表单都应包含 CSRF 令牌字段。否则,请求将被拒绝。您可以在 CSRF 文档 中阅读有关 CSRF 保护的更多信息。

1<form method="POST" action="/profile">
2 @csrf
3 ...
4</form>

重定向路由

如果您定义的路由需要重定向到另一个 URI,可以使用 Route::redirect 方法。此方法提供了一个便捷的快捷方式,因此您无需为了简单的重定向而定义完整的路由或控制器。

1Route::redirect('/here', '/there');

默认情况下,Route::redirect 返回 302 状态码。您可以使用可选的第三个参数自定义状态码。

1Route::redirect('/here', '/there', 301);

或者,您可以使用 Route::permanentRedirect 方法返回 301 状态码。

1Route::permanentRedirect('/here', '/there');

在重定向路由中使用路由参数时,以下参数被 Laravel 保留,不能使用:destinationstatus

视图路由

如果您的路由只需要返回一个 视图,可以使用 Route::view 方法。与 redirect 方法一样,此方法提供了一个简单的快捷方式,无需定义完整的路由或控制器。view 方法接受 URI 作为第一个参数,视图名称作为第二个参数。此外,您可以提供一个数据数组作为可选的第三个参数传递给视图。

1Route::view('/welcome', 'welcome');
2 
3Route::view('/welcome', 'welcome', ['name' => 'Taylor']);

在视图路由中使用路由参数时,以下参数被 Laravel 保留,不能使用:viewdatastatusheaders

查看路由列表

route:list Artisan 命令可以轻松提供应用程序定义的所有路由的概览。

1php artisan route:list

默认情况下,分配给每个路由的路由中间件不会显示在 route:list 的输出中;但是,您可以通过在命令中添加 -v 选项,指示 Laravel 显示路由中间件和中间件组名称。

1php artisan route:list -v
2 
3# Expand middleware groups...
4php artisan route:list -vv

您还可以指示 Laravel 仅显示以特定 URI 开头的路由。

1php artisan route:list --path=api

此外,您可以在执行 route:list 命令时提供 --except-vendor 选项,以指示 Laravel 隐藏所有由第三方包定义的路由。

1php artisan route:list --except-vendor

同样,您也可以在执行 route:list 命令时提供 --only-vendor 选项,以指示 Laravel 仅显示由第三方包定义的路由。

1php artisan route:list --only-vendor

路由自定义

默认情况下,您的应用程序路由由 bootstrap/app.php 文件配置和加载。

1<?php
2 
3use Illuminate\Foundation\Application;
4 
5return Application::configure(basePath: dirname(__DIR__))
6 ->withRouting(
7 web: __DIR__.'/../routes/web.php',
8 commands: __DIR__.'/../routes/console.php',
9 health: '/up',
10 )->create();

然而,有时您可能希望定义一个全新的文件来包含应用程序路由的子集。为此,您可以向 withRouting 方法提供一个 then 闭包。在此闭包内,您可以注册应用程序所需的任何额外路由。

1use Illuminate\Support\Facades\Route;
2 
3->withRouting(
4 web: __DIR__.'/../routes/web.php',
5 commands: __DIR__.'/../routes/console.php',
6 health: '/up',
7 then: function () {
8 Route::middleware('api')
9 ->prefix('webhooks')
10 ->name('webhooks.')
11 ->group(base_path('routes/webhooks.php'));
12 },
13)

或者,您甚至可以通过向 withRouting 方法提供 using 闭包来完全控制路由注册。当传递此参数时,框架不会注册任何 HTTP 路由,您需要负责手动注册所有路由。

1use Illuminate\Support\Facades\Route;
2 
3->withRouting(
4 commands: __DIR__.'/../routes/console.php',
5 using: function () {
6 Route::middleware('api')
7 ->prefix('api')
8 ->group(base_path('routes/api.php'));
9 
10 Route::middleware('web')
11 ->group(base_path('routes/web.php'));
12 },
13)

路由参数

必填参数

有时您需要在路由中捕获 URI 的片段。例如,您可能需要从 URL 中捕获用户的 ID。您可以通过定义路由参数来实现。

1Route::get('/user/{id}', function (string $id) {
2 return 'User '.$id;
3});

您可以根据需要定义任意数量的路由参数。

1Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) {
2 // ...
3});

路由参数总是被包裹在 {} 花括号内,并且应该由字母字符组成。下划线 (_) 在路由参数名称中也是允许的。路由参数根据顺序被注入到路由回调/控制器中——路由回调/控制器参数的名称并不重要。

参数与依赖注入

如果您的路由有依赖项,并且希望 Laravel 服务容器自动将其注入到路由回调中,则应将路由参数放在依赖项之后。

1use Illuminate\Http\Request;
2 
3Route::get('/user/{id}', function (Request $request, string $id) {
4 return 'User '.$id;
5});

可选参数

有时您可能需要指定一个 URI 中并不总是存在的路由参数。您可以通过在参数名称后加一个 ? 来实现。请确保为路由的相应变量设置默认值。

1Route::get('/user/{name?}', function (?string $name = null) {
2 return $name;
3});
4 
5Route::get('/user/{name?}', function (?string $name = 'John') {
6 return $name;
7});

正则表达式约束

您可以使用路由实例上的 where 方法来约束路由参数的格式。where 方法接受参数名称和一个定义参数约束方式的正则表达式。

1Route::get('/user/{name}', function (string $name) {
2 // ...
3})->where('name', '[A-Za-z]+');
4 
5Route::get('/user/{id}', function (string $id) {
6 // ...
7})->where('id', '[0-9]+');
8 
9Route::get('/user/{id}/{name}', function (string $id, string $name) {
10 // ...
11})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

为方便起见,一些常用的正则表达式模式具有辅助方法,允许您快速向路由添加模式约束。

1Route::get('/user/{id}/{name}', function (string $id, string $name) {
2 // ...
3})->whereNumber('id')->whereAlpha('name');
4 
5Route::get('/user/{name}', function (string $name) {
6 // ...
7})->whereAlphaNumeric('name');
8 
9Route::get('/user/{id}', function (string $id) {
10 // ...
11})->whereUuid('id');
12 
13Route::get('/user/{id}', function (string $id) {
14 // ...
15})->whereUlid('id');
16 
17Route::get('/category/{category}', function (string $category) {
18 // ...
19})->whereIn('category', ['movie', 'song', 'painting']);
20 
21Route::get('/category/{category}', function (string $category) {
22 // ...
23})->whereIn('category', CategoryEnum::cases());

如果传入的请求不符合路由模式约束,将返回 404 HTTP 响应。

全局约束

如果您希望某个路由参数始终受到特定正则表达式的约束,可以使用 pattern 方法。您应该在应用程序 App\Providers\AppServiceProvider 类的 boot 方法中定义这些模式。

1use Illuminate\Support\Facades\Route;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Route::pattern('id', '[0-9]+');
9}

一旦定义了模式,它就会自动应用于所有使用该参数名称的路由。

1Route::get('/user/{id}', function (string $id) {
2 // Only executed if {id} is numeric...
3});

编码的正斜杠

Laravel 路由组件允许在路由参数值中包含除 / 以外的所有字符。您必须使用 where 条件正则表达式显式允许 / 作为占位符的一部分。

1Route::get('/search/{search}', function (string $search) {
2 return $search;
3})->where('search', '.*');

编码的正斜杠仅在最后一个路由片段中受支持。

命名路由

命名路由允许为特定路由方便地生成 URL 或重定向。您可以通过在路由定义上链式调用 name 方法来为路由指定名称。

1Route::get('/user/profile', function () {
2 // ...
3})->name('profile');

您也可以为控制器操作指定路由名称。

1Route::get(
2 '/user/profile',
3 [UserProfileController::class, 'show']
4)->name('profile');

路由名称应该是唯一的。

为命名路由生成 URL

一旦为给定路由分配了名称,您就可以在通过 Laravel 的 routeredirect 辅助函数生成 URL 或重定向时使用路由名称。

1// Generating URLs...
2$url = route('profile');
3 
4// Generating Redirects...
5return redirect()->route('profile');
6 
7return to_route('profile');

如果命名路由定义了参数,您可以将参数作为第二个参数传递给 route 函数。给定的参数将自动插入到生成 URL 的正确位置。

1Route::get('/user/{id}/profile', function (string $id) {
2 // ...
3})->name('profile');
4 
5$url = route('profile', ['id' => 1]);

如果您在数组中传递额外参数,这些键值对将自动添加到生成 URL 的查询字符串中。

1Route::get('/user/{id}/profile', function (string $id) {
2 // ...
3})->name('profile');
4 
5$url = route('profile', ['id' => 1, 'photos' => 'yes']);
6 
7// http://example.com/user/1/profile?photos=yes

有时,您可能希望为 URL 参数指定请求范围内的默认值,例如当前语言环境。为此,您可以使用 URL::defaults 方法

检查当前路由

如果您想确定当前请求是否路由到了特定的命名路由,可以在路由实例上使用 named 方法。例如,您可以从路由中间件中检查当前路由名称。

1use Closure;
2use Illuminate\Http\Request;
3use Symfony\Component\HttpFoundation\Response;
4 
5/**
6 * Handle an incoming request.
7 *
8 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
9 */
10public function handle(Request $request, Closure $next): Response
11{
12 if ($request->route()->named('profile')) {
13 // ...
14 }
15 
16 return $next($request);
17}

路由组

路由组允许您在大量路由之间共享路由属性(如中间件),而无需在每个单独的路由上定义这些属性。

嵌套组会尝试智能地将其属性与父组“合并”。中间件和 where 条件会被合并,而名称和前缀会被追加。命名空间分隔符和 URI 前缀中的斜杠会在适当的时候自动添加。

中间件

要将 中间件 分配给组内的所有路由,您可以在定义组之前使用 middleware 方法。中间件将按照它们在数组中列出的顺序执行。

1Route::middleware(['first', 'second'])->group(function () {
2 Route::get('/', function () {
3 // Uses first & second middleware...
4 });
5 
6 Route::get('/user/profile', function () {
7 // Uses first & second middleware...
8 });
9});

控制器

如果一组路由都使用相同的 控制器,您可以使用 controller 方法为组内的所有路由定义通用控制器。然后,在定义路由时,您只需提供它们调用的控制器方法即可。

1use App\Http\Controllers\OrderController;
2 
3Route::controller(OrderController::class)->group(function () {
4 Route::get('/orders/{id}', 'show');
5 Route::post('/orders', 'store');
6});

子域名路由

路由组也可用于处理子域名路由。子域名可以像路由 URI 一样分配路由参数,允许您捕获子域名的一部分以在路由或控制器中使用。子域名可以通过在定义组之前调用 domain 方法来指定。

1Route::domain('{account}.example.com')->group(function () {
2 Route::get('/user/{id}', function (string $account, string $id) {
3 // ...
4 });
5});

路由前缀

prefix 方法可用于为组内的每个路由添加 URI 前缀。例如,您可能希望为组内的所有路由 URI 添加 admin 前缀。

1Route::prefix('admin')->group(function () {
2 Route::get('/users', function () {
3 // Matches The "/admin/users" URL
4 });
5});

路由名称前缀

name 方法可用于为组内的每个路由名称添加前缀字符串。例如,您可能希望为组内所有路由的名称添加 admin 前缀。给定的字符串会按指定方式直接作为前缀添加到路由名称中,因此请确保在后缀中包含 . 字符。

1Route::name('admin.')->group(function () {
2 Route::get('/users', function () {
3 // Route assigned name "admin.users"...
4 })->name('users');
5});

路由模型绑定

当向路由或控制器操作注入模型 ID 时,您通常会查询数据库以检索与该 ID 对应的模型。Laravel 路由模型绑定提供了一种将模型实例直接自动注入到路由中的便捷方法。例如,您无需注入用户 ID,而是可以直接注入匹配该 ID 的完整 User 模型实例。

隐式绑定

Laravel 会自动解析路由或控制器操作中定义的 Eloquent 模型,其类型提示变量名称必须与路由片段名称匹配。例如:

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5});

由于 $user 变量被类型提示为 App\Models\User Eloquent 模型,并且变量名称与 {user} URI 片段匹配,Laravel 将自动注入 ID 与请求 URI 中的相应值匹配的模型实例。如果在数据库中找不到匹配的模型实例,将自动生成 404 HTTP 响应。

当然,在使用控制器方法时也可以进行隐式绑定。同样请注意,{user} URI 片段与控制器中包含 App\Models\User 类型提示的 $user 变量相匹配。

1use App\Http\Controllers\UserController;
2use App\Models\User;
3 
4// Route definition...
5Route::get('/users/{user}', [UserController::class, 'show']);
6 
7// Controller method definition...
8public function show(User $user)
9{
10 return view('user.profile', ['user' => $user]);
11}

软删除模型

通常,隐式模型绑定不会检索已被 软删除 的模型。但是,您可以通过在路由定义上链式调用 withTrashed 方法来指示隐式绑定检索这些模型。

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5})->withTrashed();

自定义键

有时您可能希望使用 id 以外的列来解析 Eloquent 模型。为此,您可以在路由参数定义中指定该列。

1use App\Models\Post;
2 
3Route::get('/posts/{post:slug}', function (Post $post) {
4 return $post;
5});

如果您希望模型绑定在检索特定模型类时始终使用除 id 之外的数据库列,则可以覆盖 Eloquent 模型上的 getRouteKeyName 方法。

1/**
2 * Get the route key for the model.
3 */
4public function getRouteKeyName(): string
5{
6 return 'slug';
7}

自定义键与作用域

当在单个路由定义中隐式绑定多个 Eloquent 模型时,您可能希望对第二个 Eloquent 模型进行作用域限定,使其必须是前一个 Eloquent 模型的子级。例如,考虑此通过特定用户的 slug 检索博客文章的路由定义:

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
5 return $post;
6});

当将自定义键隐式绑定用作嵌套路由参数时,Laravel 会自动将查询范围限定为通过其父级检索嵌套模型,并使用约定来猜测父级上的关系名称。在这种情况下,它将假定 User 模型具有名为 posts(路由参数名称的复数形式)的关系,该关系可用于检索 Post 模型。

如果您愿意,可以指示 Laravel 即使在未提供自定义键的情况下也对“子”绑定进行作用域限定。为此,您可以在定义路由时调用 scopeBindings 方法。

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
5 return $post;
6})->scopeBindings();

或者,您可以指示整组路由定义使用作用域绑定。

1Route::scopeBindings()->group(function () {
2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
3 return $post;
4 });
5});

同样,您可以通过调用 withoutScopedBindings 方法显式指示 Laravel 不要对绑定进行作用域限定。

1Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
2 return $post;
3})->withoutScopedBindings();

自定义模型缺失行为

通常,如果找不到隐式绑定的模型,会生成 404 HTTP 响应。但是,您可以通过在定义路由时调用 missing 方法来自定义此行为。missing 方法接受一个闭包,如果找不到隐式绑定的模型,该闭包将被调用。

1use App\Http\Controllers\LocationsController;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\Redirect;
4 
5Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
6 ->name('locations.view')
7 ->missing(function (Request $request) {
8 return Redirect::route('locations.index');
9 });

隐式枚举绑定

PHP 8.1 引入了对 枚举 (Enums) 的支持。为了补充此功能,Laravel 允许您在路由定义上对 字符串支持的枚举 进行类型提示,只有当该路由片段对应于有效的枚举值时,Laravel 才会调用该路由。否则,将自动返回 404 HTTP 响应。例如,给定以下枚举:

1<?php
2 
3namespace App\Enums;
4 
5enum Category: string
6{
7 case Fruits = 'fruits';
8 case People = 'people';
9}

您可以定义一个只有当 {category} 路由片段为 fruitspeople 时才会调用的路由。否则,Laravel 将返回 404 HTTP 响应。

1use App\Enums\Category;
2use Illuminate\Support\Facades\Route;
3 
4Route::get('/categories/{category}', function (Category $category) {
5 return $category->value;
6});

显式绑定

您无需使用 Laravel 基于约定的隐式模型解析即可使用模型绑定。您还可以显式定义路由参数与模型之间的对应关系。要注册显式绑定,请使用路由器的 model 方法来指定特定参数的类。您应该在 AppServiceProvider 类的 boot 方法的开头定义显式模型绑定。

1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Route::model('user', User::class);
10}

接下来,定义一个包含 {user} 参数的路由。

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 // ...
5});

由于我们已将所有 {user} 参数绑定到 App\Models\User 模型,该类的实例将被注入到路由中。因此,例如,对 users/1 的请求将注入数据库中 ID 为 1User 实例。

如果在数据库中找不到匹配的模型实例,将自动生成 404 HTTP 响应。

自定义解析逻辑

如果您希望定义自己的模型绑定解析逻辑,可以使用 Route::bind 方法。传递给 bind 方法的闭包将接收 URI 片段的值,并应返回应注入到路由中的类实例。同样,此自定义应在应用程序 AppServiceProviderboot 方法中进行。

1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Route::bind('user', function (string $value) {
10 return User::where('name', $value)->firstOrFail();
11 });
12}

或者,您可以覆盖 Eloquent 模型上的 resolveRouteBinding 方法。此方法将接收 URI 片段的值,并应返回应注入到路由中的类实例。

1/**
2 * Retrieve the model for a bound value.
3 *
4 * @param mixed $value
5 * @param string|null $field
6 * @return \Illuminate\Database\Eloquent\Model|null
7 */
8public function resolveRouteBinding($value, $field = null)
9{
10 return $this->where('name', $value)->firstOrFail();
11}

如果路由正在使用 隐式绑定作用域,则 resolveChildRouteBinding 方法将用于解析父模型的子绑定。

1/**
2 * Retrieve the child model for a bound value.
3 *
4 * @param string $childType
5 * @param mixed $value
6 * @param string|null $field
7 * @return \Illuminate\Database\Eloquent\Model|null
8 */
9public function resolveChildRouteBinding($childType, $value, $field)
10{
11 return parent::resolveChildRouteBinding($childType, $value, $field);
12}

回退路由

使用 Route::fallback 方法,您可以定义一个当没有其他路由与传入请求匹配时执行的路由。通常,未处理的请求会通过应用程序的异常处理程序自动呈现“404”页面。但是,由于您通常会在 routes/web.php 文件中定义 fallback 路由,因此 web 中间件组中的所有中间件都将应用于该路由。您可以根据需要自由地向此路由添加额外的中间件。

1Route::fallback(function () {
2 // ...
3});

速率限制

定义速率限制器

Laravel 包含了强大且可自定义的速率限制服务,您可以利用它来限制给定路由或路由组的流量。要开始使用,您应该定义满足应用程序需求的速率限制器配置。

速率限制器可以在应用程序 App\Providers\AppServiceProvider 类的 boot 方法中定义。

1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 RateLimiter::for('api', function (Request $request) {
11 return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
12 });
13}

速率限制器是使用 RateLimiter 门面的 for 方法定义的。for 方法接受一个速率限制器名称和一个返回限制配置的闭包,该配置将应用于分配给该速率限制器的路由。限制配置是 Illuminate\Cache\RateLimiting\Limit 类的实例。此类包含有用的“构建器”方法,以便您可以快速定义限制。速率限制器名称可以是您想要的任何字符串。

1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 RateLimiter::for('global', function (Request $request) {
11 return Limit::perMinute(1000);
12 });
13}

如果传入的请求超过了指定的速率限制,Laravel 将自动返回一个 429 HTTP 状态码的响应。如果您想定义自己的速率限制响应,可以使用 response 方法。

1RateLimiter::for('global', function (Request $request) {
2 return Limit::perMinute(1000)->response(function (Request $request, array $headers) {
3 return response('Custom response...', 429, $headers);
4 });
5});

由于速率限制器回调会接收传入的 HTTP 请求实例,因此您可以根据传入的请求或已验证身份的用户动态构建适当的速率限制。

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()?->vipCustomer()
3 ? Limit::none()
4 : Limit::perHour(10);
5});

分段速率限制

有时您可能希望按某个任意值对速率限制进行分段。例如,您可能希望允许用户按每个 IP 地址每分钟访问给定路由 100 次。为此,您可以在构建速率限制时使用 by 方法。

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100)->by($request->ip());
5});

为了说明此功能,我们可以在另一个示例中将路由访问限制为:每个已验证用户 ID 每分钟 100 次,或每个访客 IP 地址每分钟 10 次。

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()
3 ? Limit::perMinute(100)->by($request->user()->id)
4 : Limit::perMinute(10)->by($request->ip());
5});

多重速率限制

如果需要,您可以为给定的速率限制器配置返回一个速率限制数组。每个速率限制将根据它们在数组中的放置顺序对路由进行评估。

1RateLimiter::for('login', function (Request $request) {
2 return [
3 Limit::perMinute(500),
4 Limit::perMinute(3)->by($request->input('email')),
5 ];
6});

如果您要分配多个由相同 by 值分段的速率限制,请确保每个 by 值都是唯一的。实现这一点的最简单方法是为传递给 by 方法的值添加前缀。

1RateLimiter::for('uploads', function (Request $request) {
2 return [
3 Limit::perMinute(10)->by('minute:'.$request->user()->id),
4 Limit::perDay(1000)->by('day:'.$request->user()->id),
5 ];
6});

基于响应的速率限制

除了限制传入请求的速率外,Laravel 还允许您使用 after 方法根据响应进行速率限制。当您只想计算某些响应(例如验证错误、404 响应或其他特定 HTTP 状态码)对速率限制的影响时,这非常有用。

after 方法接受一个接收响应的闭包,如果响应应计入速率限制,则应返回 true,如果应忽略,则返回 false。这对于通过限制连续的 404 响应来防止枚举攻击,或者允许用户重试验证失败的请求而不耗尽其在仅应限制成功操作的端点上的速率限制特别有用。

1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4use Symfony\Component\HttpFoundation\Response;
5 
6RateLimiter::for('resource-not-found', function (Request $request) {
7 return Limit::perMinute(10)
8 ->by($request->user()?->id ?: $request->ip())
9 ->after(function (Response $response) {
10 // Only count 404 responses toward the rate limit to prevent enumeration...
11 return $response->status() === 404;
12 });
13});

将速率限制器附加到路由

速率限制器可以使用 throttle 中间件 附加到路由或路由组。throttle 中间件接受您希望分配给路由的速率限制器名称。

1Route::middleware(['throttle:uploads'])->group(function () {
2 Route::post('/audio', function () {
3 // ...
4 });
5 
6 Route::post('/video', function () {
7 // ...
8 });
9});

使用 Redis 进行限流

默认情况下,throttle 中间件映射到 Illuminate\Routing\Middleware\ThrottleRequests 类。但是,如果您使用 Redis 作为应用程序的缓存驱动程序,则可能希望指示 Laravel 使用 Redis 来管理速率限制。为此,您应该在应用程序的 bootstrap/app.php 文件中使用 throttleWithRedis 方法。此方法将 throttle 中间件映射到 Illuminate\Routing\Middleware\ThrottleRequestsWithRedis 中间件类。

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->throttleWithRedis();
3 // ...
4})

表单方法伪造

HTML 表单不支持 PUTPATCHDELETE 操作。因此,在定义从 HTML 表单调用的 PUTPATCHDELETE 路由时,您需要向表单添加一个隐藏的 _method 字段。随 _method 字段发送的值将被用作 HTTP 请求方法。

1<form action="/example" method="POST">
2 <input type="hidden" name="_method" value="PUT">
3 <input type="hidden" name="_token" value="{{ csrf_token() }}">
4</form>

为方便起见,您可以使用 @method Blade 指令 来生成 _method 输入字段。

1<form action="/example" method="POST">
2 @method('PUT')
3 @csrf
4</form>

访问当前路由

您可以在 Route 门面上使用 currentcurrentRouteNamecurrentRouteAction 方法来访问有关处理传入请求的路由的信息。

1use Illuminate\Support\Facades\Route;
2 
3$route = Route::current(); // Illuminate\Routing\Route
4$name = Route::currentRouteName(); // string
5$action = Route::currentRouteAction(); // string

您可以参考 API 文档中关于 Route 门面的底层类Route 实例 的内容,以查看路由器和路由类上所有可用的方法。

跨域资源共享 (CORS)

Laravel 可以自动响应您配置的值的 CORS OPTIONS HTTP 请求。OPTIONS 请求将由自动包含在应用程序全局中间件堆栈中的 HandleCors 中间件 自动处理。

有时,您可能需要为您的应用程序自定义 CORS 配置值。您可以使用 config:publish Artisan 命令发布 cors 配置文件来完成此操作。

1php artisan config:publish cors

此命令将在应用程序的 config 目录中放置一个 cors.php 配置文件。

有关 CORS 和 CORS 头部的更多信息,请参阅 MDN 关于 CORS 的 Web 文档

路由缓存

将应用程序部署到生产环境时,您应该利用 Laravel 的路由缓存。使用路由缓存将极大地减少注册所有应用程序路由所需的时间。要生成路由缓存,请执行 route:cache Artisan 命令。

1php artisan route:cache

运行此命令后,您的缓存路由文件将在每次请求时加载。请记住,如果您添加了任何新路由,则需要生成新的路由缓存。因此,您只应在项目部署期间运行 route:cache 命令。

您可以使用 route:clear 命令来清除路由缓存。

1php artisan route:clear