【RuoYi-Eggjs】:限流,为 API 保驾护航
前言
在 Web 应用开发中,保护 API 免受恶意攻击和过载是一项重要的安全措施。无论是防止暴力破解、恶意刷接口,还是保护服务器免于过载,限流(Rate Limiting)都是必不可少的防护手段。[ruoyi-eggjs-ratelimiter](https://github.com/undsky/ruoyi-eggjs-ratelimiter) 就是一个为 Egg.js 量身定制的限流插件,基于强大的 rate-limiter-flexible 库,提供开箱即用的请求限流能力。
为什么需要限流?
🛡️ 防止暴力攻击
登录、注册、找回密码等接口最容易成为暴力破解的目标。通过限流,可以有效限制单个 IP 在短时间内的尝试次数:
攻击者 1 分钟内尝试 1000 次登录 ❌ 被限流
正常用户 1 分钟内尝试 3 次登录 ✅ 正常访问
🚦 保护服务器资源
防止单个用户或恶意脚本占用过多服务器资源,确保所有用户都能获得良好的服务质量。
核心特性
🎯 基于 IP 的智能限流
插件自动获取客户端真实 IP 地址,并基于 IP 进行限流统计。支持反向代理场景,可正确识别经过 Nginx 等代理的真实 IP。
💾 双存储模式
1. 内存存储(Memory)
- 优势:速度快,无需额外服务
- 劣势:应用重启后限流记录丢失
- 适用:开发环境、单机部署
2. Redis 存储(推荐)
- 优势:数据持久化,多实例共享限流状态
- 劣势:需要 Redis 服务
- 适用:生产环境、集群部署
📈 标准响应头
插件严格遵循 HTTP 限流标准,返回标准的响应头:
Retry-After: 5.5 # 多少秒后可以重试
X-RateLimit-Limit: 100 # 时间窗口内的请求限制
X-RateLimit-Remaining: 0 # 剩余可用请求次数
X-RateLimit-Reset: Mon Jan 01... # 限流重置时间
前端可以通过这些响应头实现友好的用户提示和自动重试机制。
🔍 自动日志记录
当请求被限流时,插件会自动记录详细日志,包括 IP 地址、用户信息、请求内容等,便于追踪和分析恶意行为。
快速上手
安装
npm i ruoyi-eggjs-ratelimiter --save
配置
1. 启用插件
// config/plugin.js
exports.ratelimiter = {
enable: true,
package: "ruoyi-eggjs-ratelimiter",
};
2. 基础配置(内存存储)
适合开发环境或单机部署:
// config/config.default.js
config.ratelimiter = {
points: 1000, // 每个时间窗口允许 1000 次请求
duration: 1000, // 时间窗口为 1000 毫秒(1 秒)
redis: null, // 不使用 Redis
};
3. Redis 配置(生产环境推荐)
适合生产环境和集群部署:
// config/config.prod.js
config.ratelimiter = {
points: 100, // 每分钟允许 100 次请求
duration: 60, // 时间窗口为 60 秒
redis: {
port: 6379,
host: '127.0.0.1',
password: 'your-password',
db: 0,
},
};
配置参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| points | Number | 1000 | 时间窗口内允许的最大请求次数 |
| duration | Number | 1000 | 时间窗口大小(内存:毫秒;Redis:秒) |
| redis | Object | null | Redis 配置对象,为 null 时使用内存存储 |
注意:
duration的单位根据存储方式不同而不同!使用 Redis 时单位为秒,使用内存时单位为毫秒。
实战场景
场景 1:防止登录暴力破解
登录接口是最容易被攻击的目标,限制每分钟的尝试次数可以有效防止暴力破解:
// config/config.prod.js
config.ratelimiter = {
points: 5, // 每分钟最多 5 次尝试
duration: 60, // 60 秒
redis: {
host: '127.0.0.1',
port: 6379,
},
};
效果:
- 正常用户输错密码几次后需要等待 1 分钟
- 暴力破解程序在 5 次尝试后被阻止
- 大大降低了账号被破解的风险
场景 2:通用 API 限流
对于开放的 API 接口,限制每秒的请求次数可以防止单个客户端占用过多资源:
// config/config.prod.js
config.ratelimiter = {
points: 10, // 每秒最多 10 次请求
duration: 1, // 1 秒
redis: {
host: '127.0.0.1',
port: 6379,
},
};
效果:
- 正常用户每秒 10 次请求足够使用
- 恶意脚本无法快速刷接口
- 服务器负载保持在可控范围
场景 3:开发环境宽松限制
开发环境下希望限流不影响调试,可以设置较大的限流值:
// config/config.local.js
config.ratelimiter = {
points: 1000, // 每秒 1000 次请求
duration: 1000, // 1000 毫秒(1 秒)
redis: null, // 使用内存存储,简单快速
};
场景 4:配合 Nginx 获取真实 IP
在使用 Nginx 等反向代理的情况下,需要正确配置才能获取真实 IP:
Nginx 配置:
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:7001;
}
Egg.js 配置:
// config/config.default.js
config.proxy = true; // 启用代理模式,信任 X-Forwarded-For
这样插件就能通过 ctx.request.ip 获取到真实的客户端 IP,而不是 Nginx 的 IP。
测试限流
使用 curl 测试
快速发送多个请求观察限流效果:
# 快速发送 10 个请求
for i in {1..10}; do
curl -i http://localhost:7001/api/test
done
观察响应变化
前几次请求(正常):
HTTP/1.1 200 OK
Content-Type: application/json
{"code":200,"data":{...}}
超限后的请求:
HTTP/1.1 200 OK
Retry-After: 0.5
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: Wed%2C%2027%20Nov%202024%2013%3A30%3A00%20GMT
{"code":429,"message":"Too Many Requests"}
前端处理示例
// 前端请求拦截器
axios.interceptors.response.use(
response => {
if (response.data.code === 429) {
const retryAfter = response.headers['retry-after'];
message.error(`请求过于频繁,请在 ${retryAfter} 秒后重试`);
}
return response;
}
);
常见问题
Q1: 为什么选择 Redis 还是内存?
| 存储方式 | 速度 | 持久化 | 分布式 | 适用场景 |
|---|---|---|---|---|
| 内存 | ⚡️ 极快 | ❌ 重启丢失 | ❌ 单机 | 开发环境、单机部署 |
| Redis | 🚀 快 | ✅ 持久化 | ✅ 共享 | 生产环境、集群部署 |
推荐做法:
- 开发环境使用内存存储,简单快速
- 生产环境使用 Redis,数据可靠、支持集群
Q2: duration 单位为什么不一致?
这是因为 rate-limiter-flexible 库的不同实现对时间单位的要求不同:
- RateLimiterRedis:要求
duration为秒 - RateLimiterMemory:支持
duration为毫秒
建议在配置时明确注释单位,避免混淆:
config.ratelimiter = {
points: 100,
duration: 60, // 秒(使用 Redis)
redis: { ... },
};
Q3: 如何针对不同接口设置不同限流策略?
当前插件作为全局中间件运行,对所有接口使用统一的限流策略。如果需要细粒度控制,可以考虑:
方案 1:在特定 Controller 中自定义限流逻辑
// app/controller/login.js
async login() {
// 针对登录接口的特殊限流逻辑
const limiter = new RateLimiter({ points: 5, duration: 60 });
await limiter.consume(ctx.request.ip);
// 业务逻辑
}
方案 2:扩展插件,支持路由级别的配置(需要修改插件代码)
Q4: 如何处理代理场景下的 IP 获取?
确保 Nginx 配置正确转发真实 IP:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
并在 Egg.js 中启用代理模式:
config.proxy = true;
性能优化建议
1. 合理设置限流参数
根据业务场景选择合适的 points 和 duration:
// 登录接口:严格限制
{ points: 5, duration: 60 }
// 查询接口:适度限制
{ points: 100, duration: 60 }
// 静态资源:宽松限制
{ points: 1000, duration: 60 }
2. Redis 连接优化
使用连接池,避免频繁创建连接:
config.ratelimiter = {
redis: {
host: '127.0.0.1',
port: 6379,
// 连接池配置
maxRetriesPerRequest: 1,
enableOfflineQueue: false,
},
};
3. 监控限流情况
定期查看日志,分析被限流的请求:
# 查看限流日志
grep "RateLimiter" logs/common-error.log
根据日志调整限流策略,平衡安全性和用户体验。
总结
ruoyi-eggjs-ratelimiter 是一个简单但强大的 Egg.js 限流插件,它的优势在于:
- 开箱即用:一行配置,全局生效
- 灵活存储:支持 Redis 和内存两种方式
- 标准化:遵循 HTTP 限流标准,响应头完整
- 易于调试:自动记录日志,便于追踪
- 性能优异:基于成熟的 rate-limiter-flexible 库
无论是保护登录接口免受暴力攻击,还是限制 API 调用频率,这个插件都能轻松胜任。如果你正在使用 Egg.js,强烈建议集成这个插件,为你的 API 加上一道安全防线!
- 插件地址:ruoyi-eggjs-ratelimiter
- 项目地址:RuoYi-Eggjs
- 开发文档:RuoYi-Eggjs 文档
6万+

被折叠的 条评论
为什么被折叠?



