> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 限流的自定义

> 深入 Laravel 的 RateLimiter Facade，讲解按用户、套餐、IP 地址实现自定义限流，以及基于 Redis 的高可用配置。

## `RateLimiter` Facade 的机制

Laravel 的限流由 `Illuminate\Cache\RateLimiting\Limit` 类与 `RateLimiter` Facade 组成。内部将计数器保存在缓存驱动中（默认为 file 或 redis），以此追踪请求数。

`throttle` 中间件对接收到的请求执行由 `RateLimiter::for()` 定义的闭包，一旦达到限制便返回 `429 Too Many Requests`。

## 在 `AppServiceProvider` 中定义自定义 Limiter

限流配置写在 `App\Providers\AppServiceProvider` 的 `boot()` 方法中。

```php theme={null}
<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }
}
```

`RateLimiter::for()` 的第 1 个参数是 Limiter 名，在 `throttle` 中间件中引用。第 2 个参数的闭包需要返回 `Illuminate\Cache\RateLimiting\Limit` 实例。

## 按用户、IP、套餐限流

### 对已登录用户与访客采用不同限制

```php theme={null}
RateLimiter::for('uploads', function (Request $request) {
    return $request->user()
        ? Limit::perHour(100)->by($request->user()->id)
        : Limit::perHour(10)->by($request->ip());
});
```

### 根据用户套餐限制

```php theme={null}
RateLimiter::for('api', function (Request $request) {
    $user = $request->user();

    if (! $user) {
        return Limit::perMinute(30)->by($request->ip());
    }

    return match ($user->plan) {
        'enterprise' => Limit::none(),
        'pro'        => Limit::perMinute(500)->by($user->id),
        default      => Limit::perMinute(60)->by($user->id),
    };
});
```

### 按 IP 全局限流

无论访问哪个端点，都按 IP 限流。

```php theme={null}
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->by($request->ip());
});
```

### 组合多个限制

以数组形式返回时，所有限制都会被评估，只要触达其中之一就返回 `429`。

```php theme={null}
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->ip()),
        Limit::perMinute(5)->by($request->input('email')),
    ];
});
```

<Info>
  如果多个限制共享同一 `by` 值，请为 key 加上前缀以避免冲突。
</Info>

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

## 通过 `throttle` 中间件指定自定义 Limiter 名

将已定义的 Limiter 名传给 `throttle` 中间件。

```php theme={null}
use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api'])->group(function () {
    Route::get('/user', function () { /* ... */ });
    Route::post('/posts', function () { /* ... */ });
});
```

### 在 `bootstrap/app.php` 中注册

Laravel 11 之后，中间件在 `bootstrap/app.php` 中管理。

```php theme={null}
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleApi('api');
    })
    ->create();
```

## 应用于 API 路由的示例

<Steps>
  <Step title="定义 Limiter">
    在 `AppServiceProvider` 中定义多个 Limiter。

    ```php theme={null}
    public function boot(): void
    {
        // 通用 API 访问
        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()?->isPro()
                ? Limit::perHour(500)->by($request->user()->id)
                : Limit::perHour(50)->by($request->user()?->id ?: $request->ip());
        });

        // 登录尝试
        RateLimiter::for('login', function (Request $request) {
            return [
                Limit::perMinute(10)->by($request->ip()),
                Limit::perMinute(5)->by($request->input('email')),
            ];
        });
    }
    ```
  </Step>

  <Step title="将中间件应用到路由">
    ```php theme={null}
    // routes/api.php
    use Illuminate\Support\Facades\Route;

    Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
        Route::get('/user', [\App\Http\Controllers\UserController::class, 'show']);
        Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
    });

    Route::middleware(['auth:sanctum', 'throttle:uploads'])->group(function () {
        Route::post('/uploads', [\App\Http\Controllers\UploadController::class, 'store']);
    });

    Route::middleware(['throttle:login'])->group(function () {
        Route::post('/login', [\App\Http\Controllers\AuthController::class, 'login']);
    });
    ```
  </Step>
</Steps>

## 响应头（`X-RateLimit-*`）机制

`throttle` 中间件会自动为响应加上限流信息头。

| 头                       | 说明                  |
| ----------------------- | ------------------- |
| `X-RateLimit-Limit`     | 允许的请求数              |
| `X-RateLimit-Remaining` | 剩余请求数               |
| `Retry-After`           | 下一次可请求前的秒数（仅 429 时） |
| `X-RateLimit-Reset`     | 限流重置的 UNIX 时间戳      |

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 45
X-RateLimit-Reset: 1717000000
Content-Type: application/json

{
    "message": "Too Many Requests."
}
```

### 自定义响应

```php theme={null}
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)
        ->by($request->user()?->id ?: $request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => '请求频率超出限制，请稍后重试。',
                'retry_after' => $headers['Retry-After'],
            ], 429, $headers);
        });
});
```

## 使用 `RateLimiter::attempt()` 手动检查

若不使用 `throttle` 中间件，而想在代码任意位置检查限流，可以使用 `RateLimiter::attempt()`。

```php theme={null}
use Illuminate\Support\Facades\RateLimiter;

class SmsController extends Controller
{
    public function send(Request $request): \Illuminate\Http\JsonResponse
    {
        $key = 'sms:' . $request->user()->id;

        $executed = RateLimiter::attempt(
            key: $key,
            maxAttempts: 5,
            callback: function () use ($request) {
                app(SmsService::class)->send(
                    $request->user()->phone,
                    $request->input('message')
                );
            },
            decaySeconds: 3600,  // 1 小时
        );

        if (! $executed) {
            $seconds = RateLimiter::availableIn($key);

            return response()->json([
                'message' => "SMS 发送已达上限，请 {$seconds} 秒后再试。",
            ], 429);
        }

        return response()->json(['message' => '已发送 SMS。']);
    }
}
```

### 查询与重置尝试次数

```php theme={null}
// 获取当前尝试次数
$hits = RateLimiter::attempts($key);

// 到下次重置的秒数
$seconds = RateLimiter::availableIn($key);

// 判断是否已达上限
$tooMany = RateLimiter::tooManyAttempts($key, $maxAttempts = 5);

// 手动重置计数器（例如登出后）
RateLimiter::clear($key);
```

### 登录节流示例

```php theme={null}
public function login(Request $request): mixed
{
    $key = 'login:' . $request->input('email');

    if (RateLimiter::tooManyAttempts($key, 5)) {
        $seconds = RateLimiter::availableIn($key);

        throw ValidationException::withMessages([
            'email' => "登录尝试次数过多，请 {$seconds} 秒后再试。",
        ]);
    }

    if (! Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::hit($key, 300);  // 5 分钟内计数

        throw ValidationException::withMessages([
            'email' => '邮箱或密码不正确。',
        ]);
    }

    RateLimiter::clear($key);

    return redirect()->intended('/dashboard');
}
```

## 基于响应的限流

若只想统计特定响应，使用 `after()`。以下示例只统计 404，用来防止资源枚举攻击：

```php theme={null}
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-lookup', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            return $response->getStatusCode() === 404;
        });
});
```

## 基于 Redis 的限流

只要把默认缓存驱动改为 Redis，`throttle` 中间件也会自动使用 Redis。

### Redis 驱动配置

```php theme={null}
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
```

```ini theme={null}
# .env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
```

### 使用 `throttleWithRedis`

若要使用 Redis 专用优化的节流中间件，请在 `bootstrap/app.php` 中调用 `throttleWithRedis()`。

```php theme={null}
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleWithRedis();
    })
    ->create();
```

由此 `throttle` 中间件会映射到 `ThrottleRequestsWithRedis` 类，通过 Redis 的原子操作实现精确计数。

<Warning>
  使用 `throttleWithRedis()` 前请确保 Redis 可用。若无法连接 Redis，可能导致所有请求被拒绝。
</Warning>

### 使用 Redis 的收益

* **可横向扩展** — 多个服务器实例可共享计数器
* **高精度** — 原子操作避免竞态
* **TTL 管理** — 利用 Redis 原生过期机制自动清理计数器

## 相关页面

<Card title="缓存" icon="database" href="/zh/cache">
  了解包含 Redis 在内的 Laravel 缓存驱动的配置与用法。
</Card>


## Related topics

- [创建 Boost 的自定义 Agent](/zh/advanced/boost-custom-agent.md)
- [创建 AI SDK 的自定义 Provider](/zh/advanced/ai-sdk-custom-provider.md)
- [Laravel Pulse](/zh/pulse.md)
- [Context（上下文）](/zh/context.md)
- [自定义代理](/zh/packages/laravel-copilot-sdk/custom-agents.md)
