基础路由

最基础的 Laravel 路由接收一个 URI 和一个闭包,提供了一种非常简单且富有表现力的方式来定义路由和行为,无需复杂的路由配置文件:

use Illuminate\Support\Facades\Route;

Route::get('/greeting', function () {
    return 'Hello World';
});

默认路由文件

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

对于大多数应用程序,你会首先在 routes/web.php 文件中定义路由。在浏览器中输入已定义路由的 URL,即可访问 routes/web.php 中定义的路由。例如,在浏览器中导航到 http://example.com/user 即可访问以下路由:

use App\Http\Controllers\UserController;

Route::get('/user', [UserController::class, 'index']);

API 路由

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

php artisan install:api

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

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

当然,对于应公开访问的路由,你可以随意省略 auth:sanctum 中间件。

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

->withRouting(
    api: __DIR__.'/../routes/api.php',
    apiPrefix: 'api/admin',
    // ...
)

可用的路由器方法

路由器允许你注册响应任何 HTTP 动词的路由:

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
Route::query($uri, $callback);

query 方法注册一个响应 QUERY HTTP 动词的路由,允许客户端为查询发送请求体。web 中间件组中的 QUERY 路由受 CSRF 保护约束。

有时,你可能需要注册一个响应多个 HTTP 动词的路由。可以使用 match 方法来实现。或者,你也可以使用 any 方法注册一个响应所有 HTTP 动词(包括 QUERY)的路由:

Route::match(['get', 'post'], '/', function () {
    // ...
});

Route::any('/', function () {
    // ...
});

当定义多个共享相同 URI 的路由时,使用 get、post、put、patch、delete、options 和 query 方法的路由应定义在使用 any、match 和 redirect 方法的路由之前。这可确保传入的请求能匹配到正确的路由。

依赖注入

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

use Illuminate\Http\Request;

Route::get('/users', function (Request $request) {
    // ...
});

CSRF 保护

请记住,任何指向在 web 路由文件中定义的 POST、PUT、PATCH 或 DELETE 路由的 HTML 表单,都应包含一个 CSRF 令牌字段,否则请求将被拒绝。你可以在 CSRF 文档中阅读更多关于 CSRF 保护的内容:

<form method="POST" action="/profile">
    @csrf
    ...
</form>

重定向路由

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

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

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

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

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

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

在重定向路由中使用路由参数时,以下参数由 Laravel 保留,不能使用:destination 和 status。

视图路由

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

Route::view('/welcome', 'welcome');

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

在视图路由中使用路由参数时,以下参数由 Laravel 保留,不能使用:view、data、status 和 headers。

列出你的路由

route:list Artisan 命令可以轻松地展示应用程序定义的所有路由概览:

php artisan route:list

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

php artisan route:list -v

# Expand middleware groups...
php artisan route:list -vv

你还可以指示 Laravel 仅显示 URI 包含指定字符串的路由:

php artisan route:list --path=api

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

php artisan route:list --except-vendor

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

php artisan route:list --only-vendor

路由自定义

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

<?php

use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )->create();

不过,有时你可能想定义一个全新的文件,用于包含应用程序部分路由。为此,你可以向 withRouting 方法提供一个 then 闭包。在这个闭包中,你可以注册应用程序所需的任何额外路由:

use Illuminate\Support\Facades\Route;

->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    then: function () {
        Route::middleware('api')
            ->prefix('webhooks')
            ->name('webhooks.')
            ->group(base_path('routes/webhooks.php'));
    },
)

或者,您甚至可以通过向 withRouting 方法提供一个 using 闭包,来完全掌控路由的注册。传入该参数后,框架将不会注册任何 HTTP 路由,所有路由需由您手动注册:

use Illuminate\Support\Facades\Route;

->withRouting(
    commands: __DIR__.'/../routes/console.php',
    using: function () {
        Route::middleware('api')
            ->prefix('api')
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->group(base_path('routes/web.php'));
    },
)

路由参数

必选参数

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

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

您可以根据路由的需求,定义任意数量的路由参数:

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

路由参数始终用 {} 括号包裹,且应由字母组成。路由参数名称中也可包含下划线(_)。路由参数会根据其顺序注入到路由回调或控制器中,路由回调或控制器参数的名称不影响注入。

参数与依赖注入

如果路由存在依赖,且希望 Laravel 服务容器自动将依赖注入到路由回调中,应将路由参数列在依赖之后:

use Illuminate\Http\Request;

Route::get('/user/{id}', function (Request $request, string $id) {
    return 'User '.$id;
});

可选参数

有时,您可能需要指定一个不一定总出现在 URI 中的路由参数。可通过在参数名称后添加 ? 标记来实现。请务必为路由对应的变量设置默认值:

Route::get('/user/{name?}', function (?string $name = null) {
    return $name;
});

Route::get('/user/{name?}', function (?string $name = 'John') {
    return $name;
});

正则表达式约束

您可使用路由实例上的 where 方法来限制路由参数的格式。where 方法接收参数名称和用于定义参数约束方式的正则表达式:

Route::get('/user/{name}', function (string $name) {
    // ...
})->where('name', '[A-Za-z]+');

Route::get('/user/{id}', function (string $id) {
    // ...
})->where('id', '[0-9]+');

Route::get('/user/{id}/{name}', function (string $id, string $name) {
    // ...
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

为方便使用,部分常用的正则表达式模式提供了辅助方法,可快速为路由添加模式约束:

Route::get('/user/{id}/{name}', function (string $id, string $name) {
    // ...
})->whereNumber('id')->whereAlpha('name');

Route::get('/user/{name}', function (string $name) {
    // ...
})->whereAlphaNumeric('name');

Route::get('/user/{id}', function (string $id) {
    // ...
})->whereUuid('id');

Route::get('/user/{id}', function (string $id) {
    // ...
})->whereUlid('id');

Route::get('/category/{category}', function (string $category) {
    // ...
})->whereIn('category', ['movie', 'song', 'painting']);

Route::get('/category/{category}', function (string $category) {
    // ...
})->whereIn('category', CategoryEnum::cases());

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

全局约束

如果希望路由参数始终受指定正则表达式约束,可使用 pattern 方法。应在应用的 App\Providers\AppServiceProvider 类的 boot 方法中定义这些模式:

use Illuminate\Support\Facades\Route;

/**
 * 引导应用服务。
 */
public function boot(): void
{
    Route::pattern('id', '[0-9]+');
}

模式定义完成后,会自动应用到所有使用该参数名称的路由上:

Route::get('/user/{id}', function (string $id) {
    // 仅当 {id} 为数字时执行...
});

编码的正斜杠

Laravel 路由组件允许路由参数值包含除 / 外的所有字符。若要允许 / 成为占位符的一部分,需使用 where 条件的正则表达式进行显式声明:

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

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

命名路由

命名路由可便捷地为特定路由生成 URL 或重定向。可在路由定义中链式调用 name 方法为路由指定名称:

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

也可为控制器动作指定路由名称:

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

路由名称应始终保持唯一。

生成命名路由的 URL

为路由指定名称后,可通过 Laravel 的 route 和 redirect 辅助函数,使用路由名称生成 URL 或重定向:

// 生成 URL...
$url = route('profile');

// 生成重定向...
return redirect()->route('profile');

return to_route('profile');

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

Route::get('/user/{id}/profile', function (string $id) {
    // ...
})->name('profile');

$url = route('profile', ['id' => 1]);

若在数组中传入额外参数,这些键值对会自动添加到生成 URL 的查询字符串中:

Route::get('/user/{id}/profile', function (string $id) {
    // ...
})->name('profile');

$url = route('profile', ['id' => 1, 'photos' => 'yes']);

// http://example.com/user/1/profile?photos=yes

有时,您可能希望为 URL 参数指定请求级的默认值,例如当前语言环境。要实现这一点,可使用URL::defaults 方法。

检查当前路由

若要判断当前请求是否被路由到指定命名路由,可在 Route 实例上使用 named 方法。例如,可从路由中间件中检查当前路由名称:

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * 处理传入请求。
 *
 * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
 */
public function handle(Request $request, Closure $next): Response
{
    if ($request->route()->named('profile')) {
        // ...
    }

    return $next($request);
}

路由分组

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

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

中间件

若要为分组内的所有路由分配中间件,可在定义分组前使用 middleware 方法。中间件按照数组中列出的顺序执行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // 使用 first 和 second 中间件...
    });

    Route::get('/user/profile', function () {
        // 使用 first 和 second 中间件...
    });
});

控制器

如果一组路由都使用同一个控制器,您可以使用 controller 方法为该组内的所有路由定义公共控制器。这样,在定义路由时,您只需提供它们调用的控制器方法:

use App\Http\Controllers\OrderController;

Route::controller(OrderController::class)->group(function () {
    Route::get('/orders/{id}', 'show');
    Route::post('/orders', 'store');
});

子域名路由

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

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

路由前缀

可以使用 prefix 方法为组内的每条路由添加给定的 URI 前缀。例如,您可能希望为组内所有路由的 URI 添加 admin 前缀:

Route::prefix('admin')->group(function () {
    Route::get('/users', function () {
        // 匹配 "/admin/users" URL
    });
});

路由名称前缀

可以使用 name 方法为组内每个路由名称添加给定字符串前缀。例如,您可能希望将组内所有路由的名称添加 admin 前缀。给定的字符串会按照指定的原样添加到路由名称前,因此我们务必在添加的前缀中提供末尾的 . 字符:

Route::name('admin.')->group(function () {
    Route::get('/users', function () {
        // 路由被赋予名称 "admin.users"...
    })->name('users');
});

路由模型绑定

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

隐式绑定

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

use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return $user->email;
});

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

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

use App\Http\Controllers\UserController;
use App\Models\User;

// 路由定义...
Route::get('/users/{user}', [UserController::class, 'show']);

// 控制器方法定义...
public function show(User $user)
{
    return view('user.profile', ['user' => $user]);
}

软删除模型

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

use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return $user->email;
})->withTrashed();

自定义主键

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

use App\Models\Post;

Route::get('/posts/{post:slug}', function (Post $post) {
    return $post;
});

如果希望模型绑定在检索给定模型类时,始终使用除 id 之外的数据库列,可以在 Eloquent 模型上应用 RouteKey 属性:

use Illuminate\Database\Eloquent\Attributes\RouteKey;
use Illuminate\Database\Eloquent\Model;

#[RouteKey('slug')]
class Post extends Model
{
    // ...
}

自定义主键与作用域

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

use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
});

当将带有自定义主键的隐式绑定用作嵌套路由参数时,Laravel 会自动通过作用域限制查询,利用约定猜测父模型上的关系名称来检索嵌套模型。在此情况下,系统会假设 User 模型存在一个名为 posts(即路由参数名称的复数形式)的关系,可用来检索 Post 模型。

如果需要,即使未提供自定义主键,您也可以指示 Laravel 对“子项”绑定进行作用域限制。为此,您可以在定义路由时调用 scopeBindings 方法:

use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();

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

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

同样,您可以通过调用 withoutScopedBindings 方法,明确指示 Laravel 不对绑定进行作用域限制:

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

自定义缺失模型行为

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

use App\Http\Controllers\LocationsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
    ->name('locations.view')
    ->missing(function (Request $request) {
        return Redirect::route('locations.index');
    });

隐式枚举绑定

PHP 8.1 引入了对枚举的支持。为配合这一特性,Laravel 允许您在路由定义中对字符串支持的枚举进行类型提示,Laravel 只会在该路由片段对应有效枚举值时调用该路由。否则,系统会自动返回 404 HTTP 响应。例如,给定以下枚举:

<?php

namespace App\Enums;

enum Category: string
{
    case Fruits = 'fruits';
    case People = 'people';
}

您可以定义一个路由,仅当 {category} 路由片段为 fruits 或 people 时才会被调用。否则,Laravel 将返回 404 HTTP 响应:

use App\Enums\Category;
use Illuminate\Support\Facades\Route;

Route::get('/categories/{category}', function (Category $category) {
    return $category->value;
});

显式绑定

您不必为了使用模型绑定而采用 Laravel 基于约定的隐式模型解析方式,也可以显式定义路由参数与模型的对应关系。若要注册显式绑定,请使用路由器的 model 方法为给定参数指定类。您应在 AppServiceProvider 类的 boot 方法开头定义显式模型绑定:

use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * 初始化应用服务。
 */
public function boot(): void
{
    Route::model('user', User::class);
}

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

use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    // ...
});

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

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

自定义解析逻辑

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

use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::bind('user', function (string $value) {
        return User::where('name', $value)->firstOrFail();
    });
}

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

/**
 * Retrieve the model for a bound value.
 *
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('name', $value)->firstOrFail();
}

如果路由使用了隐式绑定作用域,将使用 resolveChildRouteBinding 方法来解析父模型的子绑定:

/**
 * Retrieve the child model for a bound value.
 *
 * @param  string  $childType
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveChildRouteBinding($childType, $value, $field)
{
    return parent::resolveChildRouteBinding($childType, $value, $field);
}

回退路由

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

Route::fallback(function () {
    // ...
});

限流

定义限流器

Laravel 内置了强大且可定制的限流服务,您可以利用这些服务来限制特定路由或路由组的流量。首先,您需要定义符合应用程序需求的限流器配置。

限流器可以在应用程序的 App\Providers\AppServiceProvider 类的 boot 方法中定义:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}

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

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    RateLimiter::for('global', function (Request $request) {
        return Limit::perMinute(1000);
    });
}

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

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

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

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

分段限流

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

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

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

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

多重限流

如果需要,您可以为给定的限流器配置返回一个限流数组。路由将根据这些限流在数组中的顺序进行评估:

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

如果分配了多个按相同 by 值分段的限流,您应确保每个 by 值都是唯一的。最简单的实现方式是为传递给 by 方法的值添加前缀:

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

基于响应的限流

除了限制传入请求,Laravel 还允许您使用 after 方法根据响应进行限流。当您只想将某些响应计入限流时,例如验证错误、404 响应或其他特定 HTTP 状态码,此功能非常有用。

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

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-not-found', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            // Only count 404 responses toward the rate limit to prevent enumeration...
            return $response->status() === 404;
        });
});

将限流器附加到路由

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

Route::middleware(['throttle:uploads'])->group(function () {
    Route::post('/audio', function () {
        // ...
    });

    Route::post('/video', function () {
        // ...
    });
});

使用 Redis 进行限流

默认情况下,throttle 中间件映射到 Illuminate\Routing\Middleware\ThrottleRequests 类。不过,如果你使用 Redis 作为应用的缓存驱动,你可能需要让 Laravel 使用 Redis 来管理限流。为此,你应该在应用的 bootstrap/app.php 文件中使用 throttleWithRedis 方法。此方法会将 throttle 中间件映射到 Illuminate\Routing\Middleware\ThrottleRequestsWithRedis 中间件类:

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

表单方法伪装

HTML 表单不支持 PUT、PATCH 或 DELETE 动作。因此,在定义通过 HTML 表单调用的 PUT、PATCH 或 DELETE 路由时,你需要向表单添加一个隐藏的 _method 字段。_method 字段发送的值将被用作 HTTP 请求方法:

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

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

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

访问当前路由

你可以使用 Route 门面上的 current、currentRouteName 和 currentRouteAction 方法,来访问处理传入请求的路由相关信息:

use Illuminate\Support\Facades\Route;

$route = Route::current(); // Illuminate\Routing\Route
$name = Route::currentRouteName(); // string
$action = Route::currentRouteAction(); // string

你可以参考Route 门面底层类以及Route 实例的 API 文档,查看路由器类和路由类中所有可用的方法。

跨源资源共享(CORS)

Laravel 可以根据你配置的值,自动响应 CORS 的 OPTIONS HTTP 请求。OPTIONS 请求将由 中间件 HandleCors 自动处理,该中间件会自动包含在应用的全局中间件栈中。

有时,你可能需要为应用自定义 CORS 配置值。你可以通过 config:publish Artisan 命令发布 cors 配置文件来实现这一点:

php artisan config:publish cors

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

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

路由缓存

将应用部署到生产环境时,应充分利用 Laravel 的路由缓存。使用路由缓存会大幅减少注册应用所有路由所需的时间。若要生成路由缓存,请执行 route:cache Artisan 命令:

php artisan route:cache

运行此命令后,缓存的路由文件将在每个请求中加载。请记住,如果添加任何新路由,都需要重新生成路由缓存。因此,你应当仅在项目部署期间运行 route:cache 命令。

你可以使用 route:clear 命令清除路由缓存:

php artisan route:clear