Yii RESTful限流核心在于User类实现RateLimitInterface:必须返回秒级时间戳的getRateLimit()和loadAllowance(),Redis故障需try/catch降级,IP限流须重写loadAllowance()查Redis,响应头数值依赖实现精度。

Yii 框架的 RESTful 接口限流,核心不是配个行为就完事——RateLimiter 行为本身不带存储逻辑,它只负责调用 getRateLimit() 和 loadAllowance(),而这两个方法的实现位置、返回值含义、时间戳精度、降级策略,才是实际生效的关键。漏掉任一环,限流就形同虚设,甚至引发 500 或全站被刷垮。
必须在 User 类里实现 RateLimitInterface
Yii 的 RateLimiter 默认只认实现了 RateLimitInterface 的用户对象,且必须提供三个方法:getRateLimit()、loadAllowance()、saveAllowance()。它不会自动 fallback 到 IP 或 token,也不会读数据库字段——你得自己写。
-
getRateLimit($request, $action)必须返回数组[maxRequests, timeWindow],比如[100, 600]表示 600 秒(10 分钟)最多 100 次;漏写或返回[1, 1](默认值)会导致接口秒变不可用 -
loadAllowance($request, $action)返回[$allowance, $timestamp],其中$timestamp必须是**秒级整数**(不是毫秒),否则窗口重置逻辑错乱 - 数据库表要提前加字段:
allowance(int)和allowance_updated_at(int),迁移命令是php yii migrate/create add_allowance_and_allowance_updated_at_to_user
按 IP 限流?别动行为配置,重写 User 类里的 loadAllowance()
原生 RateLimiter 的 key 来自 $user->id,未登录用户全走 id=0,极易被游客刷爆。想按 IP 限流,不能改行为配置项,必须在 loadAllowance() 里手动构造 key 并查 Redis:
- 不要依赖
$this->allowance字段,直接用Yii::$app->redis查哈希:HMGET rate_limit_ip_{$ip} allowance timestamp - 注意:Redis 连接必须已配置好,否则抛
InvalidConfigException;连接失败时,executeCommand()会直接中断请求,造成 500 - IP 获取要用
$request->getUserIP(),别用$_SERVER['REMOTE_ADDR']——反向代理下不准
Redis 挂了,整个 API 就挂?必须加降级逻辑
RateLimiter 不内置降级,Redis 不可用时,loadAllowance() 抛异常,请求直接 500。生产环境必须兜底:
- 在
loadAllowance()里用try/catch捕获Exception或RedisException,捕获后返回宽松配额(如[PHP_INT_MAX, time()])让请求继续 - 别在
saveAllowance()里写日志或发告警——它在响应头写入前执行,阻塞主线程;应改用异步队列或记录到本地文件 - 如果用数据库存配额(非 Redis),要注意
saveAllowance()调用$this->save(false),避免触发验证规则拖慢响应
enableRateLimitHeaders=true 后,前端能直接读哪些头?
设为 true 后,RateLimiter 自动注入四个响应头,但它们的值完全取决于你 loadAllowance() 返回的计算结果:
-
X-Rate-Limit-Limit:等于getRateLimit()返回的$maxRequests -
X-Rate-Limit-Remaining:等于loadAllowance()返回的$allowance(剩余次数) -
X-Rate-Limit-Reset:等于窗口结束时间戳($lastCheckTime + $timeWindow - $allowance / $maxRequests * $timeWindow,内部算) - 超限时返回
429 Too Many Requests,但不会自动带Retry-After头——得自己在beforeAction()里补
最容易被忽略的是:所有这些头的数值,都基于你 loadAllowance() 返回的 $timestamp 是否为秒级整数。毫秒值会让 X-Rate-Limit-Reset 错乱成负数,前端无法解析。











