Search by

fiberphp / limiter

fiberphp

🚦 FiberPHP 限流器 —— Redis Hash 固定窗口计数,支持 IP 白名单(单 IP、CIDR、通配符),协程安全。

Package info

gitee.com/fiberphp/limiter.git

Issues

pkg:composer/fiberphp/limiter

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

dev-master 2026-09-09 05:54 UTC

This package is auto-updated.

Last update: 2026-09-09 05:54:59 UTC


README

基于 Redis 的限流器组件,适用于 FiberPHP 框架。采用固定窗口算法,通过 Redis Hash 分桶计数,热路径仅需一次 HINCRBY 调用,性能开销极低。内置 IP 白名单机制(支持单 IP、CIDR、通配符三种匹配方式)。

特性

  • 固定窗口限流算法,窗口按 TTL 对齐到 epoch
  • Redis Hash 按天分桶存储,热路径仅一次 HINCRBY,过期时间通过 Lua 脚本惰性延长(只增不减)
  • IP 白名单支持单 IP、CIDR(如 192.168.0.0/16)、通配符(如 192.168.*.*)三种格式
  • 提供 attempt()(返回布尔值)与 check()(超限自动抛异常)两种调用方式
  • #[RateLimit] 注解 + RateLimitMiddleware 注解式限流,零样板代码
  • 异常类与 HTTP 状态码可配置,默认返回 429 Too Many Requests

安装

composer require fiberphp/limiter

包内置 config/limiter.php 默认配置由 config 包自动合并,装包即用;如需调整,在应用 config/limiter.php 放置同名键覆盖默认值。

配置说明

配置文件位于 config/limiter.php,通过 config('limiter.xxx') 访问。

配置项类型默认值说明
enablebooltrue是否启用限流器
ip_whitelistarray[]不受频率限制的 IP 列表,支持单 IP、CIDR、通配符
exceptionstringLimitException::class超出限流时抛出的异常类,需为 Throwable 子类且支持 (string $message) 构造(建议实现 contract HttpCodeAware 声明状态码、UserFacingMessage 透传消息)

配置示例:

<?php
use FiberPHP\Limiter\LimitException;

return [
    'enable' => true,
    // 这些 ip 的请求不做频率限制
    'ip_whitelist' => [
        '127.0.0.1',          // 单 IP
        '10.0.0.0/8',         // CIDR
        '192.168.*.*',        // 通配符
    ],
    'exception' => LimitException::class,
];

注意:组件通过 FiberPHP\Redis\Sync 客户端连接 Redis,连接名为 limiter,需在 Redis 组件(fiberphp/redis)的配置中预先定义名为 limiter 的连接。

注解式限流(#[RateLimit])

#[RateLimit] 是方法级属性注解,标注在控制器方法上,配合 RateLimitMiddleware 自动执行限流——控制器无需手动调用 check()

基本用法

use FiberPHP\Limiter\Attribute\RateLimit;
use FiberPHP\Router\Attribute\Post;

class SmsController extends BaseController
{
    #[Post('/sms/send')]
    #[RateLimit(5, 1)]               // 每秒最多 5 次,key 默认 ip:{ip}
    public function send(Request $request): Response
    {
        // 限流已通过,执行业务
        return $this->success(['sent' => true]);
    }
}

限流键模板

key 参数支持 :param 路由参数占位符和 {ip} 客户端 IP 占位符:

#[RateLimit(100, 60)]                    // 每 60 秒 100 次,默认 key = ip:{ip}
#[RateLimit(5, 1, 'ip:{ip}')]            // 显式按 IP 限流
#[RateLimit(10, 60, 'user:{uid}')]       // 按路由参数 uid 限流
#[RateLimit(3, 60, 'sms:{mobile}')]      // 按请求参数 mobile 限流
  • {ip} → 客户端真实 IP($request->getRealIp()
  • :param → 路由参数或请求参数($request->input('param')

自定义提示消息

#[RateLimit(5, 1, message: '操作过于频繁,请 1 分钟后再试')]

中间件注册

在应用 config/http.php 中注册为全局中间件或别名:

// 全局注册(对所有控制器方法生效)
return [
    'middleware' => [
        'global' => [
            \FiberPHP\Limiter\Middleware\RateLimitMiddleware::class,
        ],
    ],
];

// 或别名注册(按需在路由/控制器上引用)
return [
    'middleware' => [
        'aliases' => [
            'throttle' => \FiberPHP\Limiter\Middleware\RateLimitMiddleware::class,
        ],
    ],
];

中间件仅在方法上有 #[RateLimit] 注解时执行限流,无注解的方法直接放行。IP 白名单内的请求也直接放行。

手动调用

1. 超限自动抛异常

check() 会在超出限制时自动抛出配置中指定的异常,适合在中间件或控制器入口直接调用:

use FiberPHP\Limiter\Limiter;

// 每个 IP 每分钟(60 秒)最多 100 次请求,超限抛出 LimitException(HTTP 429)
$ip = $request->getRealIp();
Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试');

2. 手动判断是否超限

attempt() 返回布尔值,由调用方自行决定后续逻辑:

use FiberPHP\Limiter\Limiter;
use FiberPHP\Limiter\LimitException;

// 同一用户每秒最多调用 5 次发短信接口
$key = 'sms:' . $userId;
if (!Limiter::attempt($key, 5, 1)) {
    throw new LimitException('操作过于频繁', 429);
}
// 继续业务逻辑

3. 结合 IP 白名单

在限流前先判断 IP 是否在白名单,白名单内的请求直接放行:

use FiberPHP\Limiter\Limiter;

$ip = $request->getRealIp();

if (!Limiter::isIpWhiteListed($ip)) {
    Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试');
}

// 处理请求

限流算法说明

组件采用 固定窗口算法(Fixed Window)

  1. 窗口对齐:窗口结束时间按 TTL 对齐到 epoch(Unix 时间戳原点)。例如 TTL 为 60 秒时,窗口边界为每分钟的整点(00:0001:0002:00 ...),所有落在同一窗口内的请求共享同一计数。
  2. 按天分桶:Redis 端以本地日期(Y-m-d)为单位,每天使用一个 Hash(limiter-YYYY-MM-DD),字段格式为 {key}-{窗口结束时间}-{ttl},值为该窗口内的累计请求数。
  3. 热路径优化:计数仅执行一次 HINCRBY。过期时间的延长通过 Lua 脚本惰性触发,且只在本地缓存认为需要延长时才调用,脚本保证过期时间只增不减。
  4. 自动回收:Hash 的过期时间覆盖当天最后一个窗口的结束时刻,过期后由 Redis 自动清理;本地进程内的过期时间缓存每 60 秒清理一次。

相比滑动窗口,固定窗口实现简单、性能更高,但在窗口边界处可能出现瞬时双倍流量(相邻两个窗口各放过 limit 次)。如对边界突刺敏感,可结合业务适当调小 limit 或缩短 ttl

IP 白名单机制

isIpWhiteListed() 依次匹配 ip_whitelist 配置中的每一项,命中任意一条即返回 true。匹配规则由 ipInRange() 实现,支持三种格式:

格式示例说明
单 IP127.0.0.1完全相等的 IPv4 地址
CIDR192.168.0.0/16通过子网掩码按位匹配网段
通配符192.168.*.** 匹配任意一段(一位或多位数字)

匹配仅针对 IPv4。白名单在首次调用时一次性懒加载到进程内存,运行期直接读取内存,无额外开销。

异常处理

超限时默认抛出 FiberPHP\Limiter\LimitException。它是纯库异常(继承 \RuntimeException), 通过 contract 契约参与框架渲染:实现 HttpCodeAware(HTTP 状态码 429)与 UserFacingMessage (消息透传给用户),在 FiberPHP 应用中未被 catch 时自动渲染为 429 响应:

namespace FiberPHP\Limiter;

use FiberPHP\Contract\Exception\HttpCodeAware;
use FiberPHP\Contract\Exception\UserFacingMessage;
use RuntimeException;

class LimitException extends RuntimeException implements HttpCodeAware, UserFacingMessage
{
    protected int $httpCode = 429;

    public function getHttpCode(): int { return $this->httpCode; }
    public function isMessageSafe(): bool { return true; }
}

可通过 exception 配置项替换为自定义异常类(需为 Throwable 子类,支持 (string $message) 构造;想自定义 HTTP 状态码/透传消息则实现 HttpCodeAware / UserFacingMessage 契约)。调用 check() 时传入的 message 会作为异常消息:

Limiter::check($key, 100, 60, '自定义提示信息');

若不希望抛异常,改用 attempt() 自行处理:

if (!Limiter::attempt($key, 100, 60)) {
    // 自定义响应
    return json(['code' => 429, 'msg' => 'Too Many Requests'])->withStatus(429);
}

目录结构

src/
├── Attribute/
│   └── RateLimit.php               # #[RateLimit] 方法级注解
├── Middleware/
│   └── RateLimitMiddleware.php     # 注解式限流中间件
├── Install.php                     # 安装钩子,定义配置路径
├── LimitException.php              # 限流异常,HTTP 429
├── Limiter.php                     # 限流器核心,提供 attempt/check/白名单能力
├── LimiterProvider.php             # 服务提供者
└── Redis.php                       # Redis 存储实现,固定窗口计数与过期管理

依赖说明

依赖说明
PHP >= 8.3运行环境要求
fiberphp/redis提供 Redis 客户端,需配置名为 limiter 的连接
fiberphp/contractHttpCodeAware / UserFacingMessage 契约
fiberphp/configconfig() 助手读取限流配置
fiberphp/containerLimiterProvider 容器绑定
fiberphp/discoveryPackageInstaller 配置文件发布
fiberphp/http(suggest)RateLimitMiddleware 依赖 Request/Response,仅注解式限流需要

命名空间为 FiberPHP\Limiter\(PSR-4,对应 src/ 目录)。

License

MIT License (c) 2026 庞斌,详见 LICENSE