thinkphp 6 授权限流需基于用户身份(如 user_id)而非 ip 实现,依赖鉴权中间件前置、稳定提取用户标识、限流逻辑置于中间件中;通过 redis+lua 原子计数,支持角色差异化阈值与标准响应头。

ThinkPHP 6 实现授权限流策略,核心是把「是否已登录」和「用户身份标识」作为限流维度,而不是仅依赖 IP。这样既能防止未授权用户滥刷接口,又能对高权限用户(如管理员、VIP)放宽限制,避免一刀切误伤。
授权限流必须满足三个前提
- 请求已通过鉴权(如 JWT、Session 或 Token 验证)
- 能稳定提取可信的用户唯一标识(如
user_id,不推荐用username或token原文) - 限流逻辑在控制器执行前完成,即必须放在中间件中
一、确保鉴权中间件先于限流中间件执行
在 app/middleware.php 中,顺序很重要:
return [
\think\middleware\SessionInit::class, // 若用 session
\app\middleware\CheckJwt::class, // 自定义 JWT 验证
\app\middleware\UserRateLimit::class, // 授权限流中间件(放在这里)
// ... 其他中间件
];
若用 throttle 内置中间件,它默认不感知登录态,所以必须自定义。
二、编写授权限流中间件(按 user_id 限流)
在 app/middleware/UserRateLimit.php 中:
<?php namespace app\middleware;
use think\facade\Cache;
use think\facade\Request;
use think\Response;
class UserRateLimit
{
protected $maxRequests = 100; // 每窗口最多请求数
protected $windowSeconds = 60; // 时间窗口(秒)
public function handle($request, \Closure $next)
{
// 1. 尝试获取已认证的用户 ID(根据你的鉴权方式调整)
$userId = $request->user()?->id ?? null;
if (!$userId) {
// 未登录用户走 IP 限流兜底,或直接拒绝
return json(['code' => 401, 'message' => '请先登录'])->code(401);
}
// 2. 构造 Redis key:带命名空间 + 用户 ID + 接口路径(避免跨接口共享)
$key = 'rate:api:' . md5($request->path()) . ':' . $userId;
// 3. 使用 Redis + Lua 原子计数(防并发超限)
$redis = \think\facade\Cache::store('redis')->handler();
$lua = eval($lua, [$key], 1, $this->windowSeconds);
// 4. 判断是否超限
if ($count > $this->maxRequests) {
$response = json([
'code' => 429,
'message' => '请求过于频繁,请稍后再试',
'retry_after' => $this->windowSeconds
])->code(429);
$response->header('Retry-After', $this->windowSeconds);
return $response;
}
return $next($request);
}
}
✅ 关键点说明:
$request->user()?->id假设你已在前置鉴权中间件中调用Auth::loginUsingId()并绑定到 Request;若用 JWT,可从 token payload 解析uid字段md5($request->path())防止 key 过长,也避免不同路由共用同一计数器- Lua 脚本保证
INCR + EXPIRE原子性,杜绝高并发下“计数超限但过期没设上”的漏洞
三、为不同角色设置差异化限流阈值
可在中间件中动态判断角色,例如:
$role = $request->user()?->role ?? 'guest';
switch ($role) {
case 'admin': $this->maxRequests = 500; $this->windowSeconds = 300; break;
case 'vip': $this->maxRequests = 200; $this->windowSeconds = 60; break;
default: $this->maxRequests = 100; $this->windowSeconds = 60; break;
}
或者更灵活地从数据库查配置:
$config = \app\model\RateLimitConfig::where('role', $role)->find();
$this->maxRequests = $config?->max_requests ?? 100;
$this->windowSeconds = $config?->window_seconds ?? 60;
四、配合前端提示(可选但推荐)
在响应头中加入标准限流字段,方便前端做友好提示或退避重试:
$response->header('X-RateLimit-Limit', $this->maxRequests);
$response->header('X-RateLimit-Remaining', max(0, $this->maxRequests - $count));
$response->header('X-RateLimit-Reset', time() + $this->windowSeconds);
不复杂但容易忽略
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











