本文目录导读:

在ThinkPHP项目中实现请求限流与防刷,主要目的是防止恶意攻击、爬虫、暴力破解以及保护服务器资源,以下是一套完整的实现方案,从配置文件、核心中间件到具体使用,针对ThinkPHP 6/8(think-orm + 中间件机制)设计。
核心方案概览
- 基于 Redis 的计数器(滑动窗口/固定窗口):最灵活,支持分布式。
- 基于中间件:ThinkPHP 6/8 支持
think\middleware\Throttle(自带)或自定义中间件。 - 基于数据库:适合低频应用,但不推荐高并发场景。
- 行为验证码:用于特定接口(如登录、注册)。
开发环境准备
安装 Redis 及 PHP 扩展
# 确保 Redis 服务已启动 redis-cli ping # 返回 PONG # PHP 扩展(建议使用 predis 或 phpredis) composer require predis/predis
配置文件(config/rate_limit.php)
<?php
return [
// 是否启用限流
'enable' => true,
// Redis 连接配置(如果独立配置)
'redis' => [
'host' => env('redis.host', '127.0.0.1'),
'port' => env('redis.port', 6379),
'password' => env('redis.password', ''),
'database' => env('redis.database', 0),
],
// 默认限流策略: key => [次数, 时间秒]
'default' => [
'limit' => 60, // 60次
'expire' => 60, // 60秒
],
// 针对特定路由的分组配置
'groups' => [
'api' => ['limit' => 100, 'expire' => 60],
'auth' => ['limit' => 5, 'expire' => 60], // 登录接口限制更严格
'upload' => ['limit' => 10, 'expire' => 300], // 上传接口
],
// 黑名单提示信息
'message' => '请求过于频繁,请稍后再试',
];
自定义限流中间件(推荐方式)
这是最灵活的做法,可以针对不同路由、不同用户/设备进行精确控制。
文件位置:app/middleware/RateLimit.php
<?php
declare(strict_types=1);
namespace app\middleware;
use Closure;
use think\Cache;
use think\Request;
use think\Response;
use Throwable;
class RateLimit
{
protected $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @param string|null $groupName 分组名称,如 api/auth
* @return Response
*/
public function handle(Request $request, Closure $next, string $groupName = 'default')
{
// 未启用则直接通过
if (!config('rate_limit.enable', true)) {
return $next($request);
}
// 1. 获取限流分组配置
$config = config("rate_limit.groups.{$groupName}", config('rate_limit.default'));
// 2. 生成唯一Key(重点)
$key = $this->buildKey($request, $groupName);
// 3. 当前时间窗口内的请求数
$limit = $config['limit'];
$expire = $config['expire'];
// 4. 使用 Redis 原子自增 + 过期时间(固定窗口算法改进)
$count = $this->increment($key, $expire);
// 5. 判断是否超限
if ($count > $limit) {
// 记录日志(可选)
trace("Rate limit exceeded: {$key} count={$count}", 'warn');
// 返回 429 状态码
return response(json_encode([
'code' => 429,
'msg' => config('rate_limit.message', '请求过于频繁,请稍后再试'),
'data' => null
]), 429, [], 'JSON');
}
// 6. 泄漏桶算法的高级扩展:可在此处添加 头部信息 或 延迟响应
return $next($request);
}
/**
* 构建唯一标识(按 IP + URL + UserAgent片段)
* 可扩展为:$request->controller . '/' . $request->action
*/
protected function buildKey(Request $request, string $group): string
{
// 优先使用用户ID(如果已登录)
$userId = 0;
if ($request->user) { // 假设有用户信息
$userId = $request->user->id;
}
$ip = $request->ip();
$uri = $request->baseUrl(); // /api/user/login
$ua = md5(substr($request->header('user-agent', ''), 0, 50));
// Key格式: rate_limit:{group}:{userId}:{ip}:{uri}:{ua}
return sprintf('rate_limit:%s:%d:%s:%s:%s', $group, $userId, $ip, md5($uri), $ua);
}
/**
* 使用缓存自增(这里使用 ThinkPHP Cache,底层支持 Redis)
*/
protected function increment(string $key, int $expire): int
{
// 使用 Redis 的 INCR + EXPIRE 组合
// 注意:ThinkPHP Cache 可能不直接支持原子操作,推荐使用 Swoole 或 Redis 客户端
// 使用 Redis 客户端直接操作(推荐)
$redis = new \Redis();
$redis->connect(config('rate_limit.redis.host'), config('rate_limit.redis.port'));
// 如果是 phpredis 扩展
if ($redis->exists($key)) {
$count = $redis->incr($key);
} else {
$count = $redis->incr($key);
$redis->expire($key, $expire);
}
return $count; // 返回当前次数
}
}
注册中间件并应用到项目
全局注册(app/middleware.php)
<?php
return [
// 全局注册限流中间件
\app\middleware\RateLimit::class,
];
按控制器/路由分组注册(推荐用于精细控制)
// app/api/middleware.php (API模块单独)
return [
RateLimit::class . ':api', // 传入分组参数 'api'
];
// 在 api 模块 routes/app.php 中
use think\facade\Route;
Route::group('user', function() {
Route::post('login', 'User/login');
})->middleware(\app\middleware\RateLimit::class . ':auth', ['except' => ['login']]);
仅限单一路由(闭包注入)
// route/app.php
Route::post('order/submit', 'Order/submit')->middleware(\app\middleware\RateLimit::class . ':api');
高级防刷策略
在中间件基础上扩展以下功能:
滑动窗口算法(避免固定窗口突发流量)
使用 Redis ZSet:
public function slidingWindow(string $key, int $window, int $limit): bool
{
$redis = new \Redis();
$currentTime = microtime(true);
$minTime = $currentTime - $window;
// 移除窗口之前的记录
$redis->zRemRangeByScore($key, 0, $minTime);
// 添加当前记录
$redis->zAdd($key, $currentTime, uniqid());
// 统计窗口内个数
$count = $redis->zCard($key);
// 设置过期时间
$redis->expire($key, $window + 1);
return $count <= $limit;
}
IP 黑名单(封禁恶意 IP)
class RateLimit
{
protected function checkIpBlacklist(string $ip): bool
{
// 在 Redis 中维护黑名单
$blackList = [
'192.168.1.1',
'10.0.0.0'
];
return in_array($ip, $blackList);
}
}
验证码联动(登录/注册接口)
登录接口建议仅使用 5 次/5分钟限制,并在连续失败后要求验证码:
// 在控制器中使用
if ($this->getFailedAttempts($username) > 5) {
// 返回错误并要求填写验证码
return json(['status' => 0, 'msg' => '请输入验证码']);
}
性能优化与注意事项
| 问题 | 解决方案 |
|---|---|
| Redis 连接开销 | 使用 Reuse/连接池(Swoole 常驻时)或使用 predis 的长连接 |
| 内部请求压力 | 在应用层做好健康检查,API 网关层做并发控制 |
| 穿透与雪崩 | 限流键设置合理过期时间,避免同时过期 |
| 分布式环境 | 必须使用 Redis(或 Token Bucket)保证数据一致性 |
| 高并发首次判断 | 使用 Lua 脚本保证原子性 |
结合前端防刷
- 前端 JS 节流:按钮点击后禁用 3 秒,防止手抖。
- 参数签名:有效防止接口重放攻击。
- 人机验证:使用验证码或滑块。
测试与监控
# 压测工具模拟并发 ab -n 1000 -c 100 http://yourdomain.com/api/order/list # 监控 Redis 中的限流键数量 redis-cli --scan --pattern "rate_limit:*"
ThinkPHP 内置 Throttle 中间件(简单方案)
如果你不想自定义,ThinkPHP 6.1+ 已内置 think\middleware\Throttle:
// config/middleware.php 或路由
Route::group('api', function () {
Route::get('list', 'Index/list');
})->middleware('throttle:60,1'); // 60次/分钟
它会根据 IP 进行限流,但功能相对简单,不适用于用户自定义规则。
- 访问频率限制:建议使用 自定义中间件 + Redis 计数(推荐固定窗口+滑动窗口结合)。
- 登录场景:必须限制 IP + 用户名双重维度。
- 高风险接口(如转账):加上 Token Bucket 算法及验证码。
- 多环境部署:确保使用 Redis 共享状态,不能使用本地缓存。
方案可直接适用于 ThinkPHP 6/8 框架,能有效抵御暴力请求,保障系统稳定。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播