应将限流逻辑抽成Service,当涉及多维度键生成、调用外部存储、含业务判断时必须抽离;ThrottleService需按四步设计:动词命名类、依赖注入、明确方法签名、剥离权限与响应;实现上分离键生成、抽象存储驱动、添加降级兜底。

当Laravel应用中多个接口需要不同策略的限流(如登录接口按IP+邮箱组合限频、支付回调按商户号限频、后台操作按用户角色分级限频),直接在中间件或控制器里硬编码规则会导致逻辑散乱、难以复用和测试。
识别限流逻辑是否该抽成Service
检查当前限流代码是否同时满足以下三点:涉及多维度键生成(IP+参数+角色)、调用外部存储(Redis/数据库)、含业务判断(白名单跳过、降级开关)。满足则必须抽离;若只是单纯调用RateLimiter::attempt()且无分支逻辑,留在中间件即可。
这一步操作起来很简单,直接把文件拖进去就行。
【不要把单行 Redis::throttle()->hit() 封装成 ThrottleService】——这种“假Service”徒增调用链,反而掩盖真实复杂度。
设计ThrottleService类结构
第一步:创建服务类文件,路径为 app/Services/ThrottleService.php,命名必须以动词开头,例如 CheckLoginRateLimitService。
第二步:构造函数只注入依赖接口,不 new 实例。例如接收 RedisInterface 和 ConfigRepositoryInterface,而非直接使用 Redis::connection()。
第三步:方法签名明确输入输出。execute() 接收数组参数 ['ip' => '192.168.1.1', 'email' => 'a@b.com'],返回 bool 或 RateLimitResult 对象,绝不返回 response() 或 redirect()。
第四步:权限校验与响应构造全部剥离。比如“是否超级管理员免限流”必须由 Policy 或 Middleware 提前判断后传入 $bypass = true,Service 内不调用 Auth::user()。
实现多策略限流逻辑
方法一:键生成策略分离
在 execute() 中先调用 private function buildKey(array $context): string,根据 $context['type'] 分支选择:login → sha1($context['ip'].$context['email']),payment → $context['merchant_id'],admin → $context['role'].'_'.$context['action']。键必须可预测、无敏感信息、长度可控。
方法二:存储驱动抽象
不写死 Redis::throttle()。定义 interface RateStore,提供 increment($key, $seconds) 和 get($key) 方法;RedisRateStore 实现它;测试时可用 InMemoryRateStore 替换,避免依赖外部服务。
客服回复模板。售前咨询、售后处理、退换货、投诉回复、好评引导、升级处理、行业FAQ、满意度挽回。Customer service reply templates for pre-sale, after-sale, returns, complaints, escalation, FAQ generation, s...
方法三:降级与兜底
当 Redis 不可用时,不抛异常中断请求。改用本地内存计数器(PHP 7.4+ 的 WeakMap)并记录 warning 日志,同时设置 header X-RateLimit-Status: degraded。这步漏掉会导致故障面扩大。
在控制器中调用限流Service
在 LoginController@store 中,删除原有 $request->ip() . $request->email 组合逻辑,改为:
$result = app(CheckLoginRateLimitService::class)->execute([
'ip' => $request->ip(),
'email' => $request->email,
'bypass' => $this->currentUserIsAdmin()
]);
if (! $result->allowed()) {
return response(['message' => 'Too many attempts'], 429)
->header('Retry-After', $result->retryAfter());
}

















