eloquent模型不能直接做速率限制,因其属于orm层,速率限制应在http请求层实现;应使用laravel中间件、控制器中ratelimiter::attempt()或routeserviceprovider中配置ratelimiter::for()规则。

PHP中Eloquent模型不能直接做速率限制
Rate limiting(速率限制)不是Eloquent的职责,它属于HTTP请求层的控制逻辑。Eloquent是ORM,只负责数据映射和持久化,getAttribute、setAttribute 或访问器(accessor/mutator)里加计数器或Redis检查,既不安全也不可靠——因为同一用户可能并发发多个请求,绕过模型层的判断;而且模型实例生命周期短,无法共享状态。
真正该用的地方是Laravel的中间件、控制器方法或API资源限流策略。
Laravel 9+ 原生rate limiter配置在app/Providers/RouteServiceProvider.php
从Laravel 9开始,速率限制规则统一由RateLimiter门面管理,不再靠throttle:60,1硬编码在路由上。你需要在boot()方法里注册自定义限流器:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
- 这个规则名叫
api,可被路由用middleware('throttle:api')引用 -
by()决定“谁”被限流:登录用户ID优先,否则 fallback 到IP,避免未登录用户互相干扰 - 别漏掉
$request->user()?->id里的空合并,否则未登录时会报Call to a member function id() on null
在控制器里手动触发限流并捕获ThrottleRequestsException
有些场景(比如登录失败后触发短信发送)需要在业务逻辑中动态限流,不能只靠路由中间件。这时要用RateLimiter::attempt():
use Illuminate\Support\Facades\RateLimiter;
$key = 'sms:'.$request->input('phone');
if (! RateLimiter::attempt($key, 5, 300)) { // 5次/5分钟
throw new ThrottleRequestsException(
'Too many attempts. Please try again later.',
429
);
}
-
$key必须带业务前缀(如sms:),否则不同功能会共用同一个计数器 - 第二个参数是最大尝试次数,第三个是窗口秒数;Laravel默认用Redis驱动,若没配Redis会退化为文件缓存,高并发下失效
- 抛出的
ThrottleRequestsException会被全局异常处理器自动转成429响应,但Header里不会自动带X-RateLimit-*字段——得自己加response()->json(...)->withHeaders([...])
不要在Eloquent accessor里调用RateLimiter::attempt()
有人想在模型里加个getRateLimitedDataAttribute(),里面做限流判断。这会导致严重问题:
- 每次
$model->rate_limited_data访问都触发一次Redis操作,N+1查询风险放大成N+1次Redis往返 - 模型可能被序列化(如放进队列、缓存),而
RateLimiter依赖Request或Session,序列化后丢失上下文 - 测试困难:没法mock请求上下文,单元测试容易挂
限流永远要放在请求进入的第一公里,而不是数据组装的最后一环。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











