> ## 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 構成。內部將 counter 保存於 cache driver（預設為 file 或 Redis），以追蹤請求數。

`throttle` 中介層對接收到的請求執行以 `RateLimiter::for()` 定義的 closure，若達到限制便回傳 `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 個引數的 closure 需回傳 `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 位址為單位進行 throttle。

```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>

## Response Header（`X-RateLimit-*`）的機制

`throttle` 中介層會自動於 Response Header 附加限制資訊。

| Header                  | 說明                   |
| ----------------------- | -------------------- |
| `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."
}
```

### 回傳自訂 Response

```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);

// 手動重置 counter（例如登出後）
RateLimiter::clear($key);
```

### 登入 throttle 範例

```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');
}
```

## 基於 Response 的速率限制

若希望僅計算特定 Response，可使用 `after()`。以下為僅計算 404 Response 防止資源列舉攻擊的範例：

```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 的速率限制

只要將預設 cache driver 變更為 Redis，`throttle` 中介層也會自動使用 Redis。

### Redis Driver 的設定

```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 專用最佳化的 throttle 中介層，於 `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 的 atomic 操作進行精確的計數。

<Warning>
  使用 `throttleWithRedis()` 時，請務必確認 Redis 可用。若對 Redis 連線失敗，所有請求可能都會被拒絕。
</Warning>

### 使用 Redis 的優點

* **水平擴展支援** — 可於多個伺服器實例間共享 counter
* **高精度** — 以 atomic 操作防止競爭條件
* **TTL 管理** — 以 Redis 原生的有效期限功能自動刪除 counter

## 相關頁面

<Card title="Cache" icon="database" href="/zh-TW/cache">
  確認包含 Redis 在內的 Laravel Cache Driver 設定與使用方式。
</Card>


## Related topics

- [InteractsWithTime trait](/zh-TW/advanced/interacts-with-time.md)
- [Laravel Fortify 與啟動套件](/zh-TW/advanced/fortify.md)
- [2026 年 3 月 Laravel 更新](/zh-TW/blog/changelog/202603.md)
- [Laravel Agent Detector — AI Agent 偵測套件](/zh-TW/blog/agent-detector-introduction.md)
- [從 Laravel 10 升級到 11](/zh-TW/blog/upgrade-10-to-11.md)
