saas应用需按租户订阅等级动态限流:免费用户严格限制,付费用户高并发保障。通过identifysubscriptiontier中间件获取tier并存入请求属性,throttlebysubscriptiontier或redis原生命令实现分级限流,敏感接口需双重校验与三层拦截。

在SaaS应用中为免费用户设置严格请求频次限制、同时保障付费用户高并发访问不被阻塞,是商业化落地的关键技术动作。直接在全局中间件里硬编码限流阈值会导致逻辑耦合、无法动态调整,且容易因租户类型识别错误导致付费用户被误限。
识别当前用户所属商业等级
在请求进入业务逻辑前,必须准确判断该用户是免费版、基础版还是企业版。Tenancy for Laravel 已自动加载租户上下文,但商业等级需从租户配置表读取,而非仅依赖用户角色。
在 app/Http/Middleware/IdentifySubscriptionTier.php 中新增中间件:
调用 Tenant::current()->subscription->tier 获取当前租户的 tier 字段值;该字段应预先存入 tenants 表或独立 subscription 表,不可通过 User 模型反查——【User 属于多个租户时,单个 User 记录无法代表当前请求所处的租户商业等级】。
将 tier 值写入 request()->attributes->set('subscription_tier', $tier) 供后续中间件使用。
按 tier 动态配置限流参数
方法一:基于 Laravel 内置 ThrottleRequests 中间件扩展
新建 app/Http/Middleware/ThrottleBySubscriptionTier.php,继承 Illuminate\Routing\Middleware\ThrottleRequests;重写 resolveRequestSignature() 方法,返回 tenant_id + subscription_tier 的组合键,避免不同租户同 tier 之间共享计数器。
在 handle() 中根据 $request->attributes->get('subscription_tier') 查表获取对应限流规则:免费版 60 次/小时,基础版 1200 次/小时,企业版 10000 次/小时;调用 parent::handle() 时传入动态计算出的 $maxAttempts 和 $decaySeconds。
方法二:使用 Redis 原生命令自定义限流(推荐)
在中间件中构造 Redis key:"throttle:{$tenantId}:{$tier}";执行 INCR + EXPIRE 原子操作;若返回值 > 阈值则 throw new ThrottleRequestsException();此方式绕过 Laravel 默认的 file 缓存驱动,避免多机部署时计数不一致。
注意:不要用 $request->ip() 作为限流维度——【SaaS 前端通常走 CDN 或反向代理,真实 IP 已被覆盖,应以 tenant_id 为唯一计量单位】。
对敏感接口实施双重限流
第一步:在路由定义中对 /api/v1/export、/api/v1/ai/generate 等高消耗接口显式绑定限流中间件:
Route::middleware(['throttle.by.tier', 'throttle.sensitive'])->group(function () { ... });
第二步:编写 throttle.sensitive 中间件,在其中对当前租户 tier 进行二次校验——若为免费用户,强制将每分钟上限压至 3 次,并拒绝携带 ?format=csv 的导出请求;该层拦截发生在业务逻辑前,防止无效请求进入模型层。
第三步:在 ExportController@handle 中再次检查 request()->attributes->get('subscription_tier') 是否为企业版;不是则直接 abort(403),不执行任何数据库查询或文件生成操作。
这一步操作起来很简单,直接把 abort(403) 放在最前面就行。











