速率限制
零依赖的速率限制器,保护 API 端点防止滥用
概述
@mono/rate-limit 提供基于滑动窗口的速率限制功能,内置内存存储,支持自定义存储(如 Redis)。
安装
pnpm add @mono/rate-limit核心概念
速率限制器通过计数每个标识(IP、用户 ID 等)在时间窗口内的请求次数来工作。超过限制的请求会被拒绝。
import { createRateLimiter } from "@mono/rate-limit";
const limiter = createRateLimiter({
limit: 10, // 每个窗口最多 10 次请求
window: 60, // 60 秒窗口
});实战示例
1. API 路由保护
最常见的用法 — 保护 API 端点免受滥用:
import { createRateLimiter, setRateLimitHeaders } from "@mono/rate-limit";
const limiter = createRateLimiter({ limit: 30, window: 60, prefix: "api" });
export async function GET(request: Request) {
const ip = request.headers.get("x-forwarded-for") || "unknown";
const result = await limiter.check(ip);
const headers = new Headers();
setRateLimitHeaders(headers, result);
if (!result.success) {
return new Response(JSON.stringify({ error: "请求过于频繁,请稍后再试" }), {
status: 429,
headers,
});
}
// 正常处理请求
return Response.json({ data: "ok" }, { headers });
}2. 登录接口保护
对认证端点使用更严格的限制:
import { createRateLimiter } from "@mono/rate-limit";
const authLimiter = createRateLimiter({
limit: 5,
window: 300, // 5 分钟内最多 5 次
prefix: "auth",
});
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") || "unknown";
const result = await authLimiter.check(ip);
if (!result.success) {
return Response.json(
{ error: "登录尝试过多,请 5 分钟后再试" },
{ status: 429 },
);
}
// 处理登录...
}3. 基于用户 ID 的限制
对已登录用户按用户 ID 限制:
import { createRateLimiter } from "@mono/rate-limit";
import { auth } from "@mono/auth";
const aiLimiter = createRateLimiter({
limit: 20,
window: 3600, // 每小时 20 次 AI 请求
prefix: "ai",
});
export async function POST(request: Request) {
const session = await auth();
if (!session?.user) {
return Response.json({ error: "未授权" }, { status: 401 });
}
const result = await aiLimiter.check(session.user.id);
if (!result.success) {
return Response.json(
{
error: "AI 请求额度已用完",
remaining: result.remaining,
resetAt: new Date(result.reset).toISOString(),
},
{ status: 429 },
);
}
// 调用 AI 服务...
}4. 多级限制
组合多个限制器实现分层保护:
import { createRateLimiter } from "@mono/rate-limit";
// 全局:每秒 100 请求
export const globalLimiter = createRateLimiter({
limit: 100,
window: 1,
prefix: "global",
});
// API:每分钟 30 请求
export const apiLimiter = createRateLimiter({
limit: 30,
window: 60,
prefix: "api",
});
// 敏感操作:每小时 10 次
export const sensitiveLimiter = createRateLimiter({
limit: 10,
window: 3600,
prefix: "sensitive",
});
export async function checkRateLimit(key: string, ...limiters: typeof globalLimiter[]) {
for (const limiter of limiters) {
const result = await limiter.check(key);
if (!result.success) return result;
}
return { success: true, limit: 0, remaining: 0, reset: 0 };
}5. 自定义 Redis 存储
在分布式环境中使用 Redis 作为后端:
import { createRateLimiter, type RateLimitStore } from "@mono/rate-limit";
import Redis from "ioredis";
class RedisStore implements RateLimitStore {
constructor(private redis: Redis) {}
async increment(key: string, window: number) {
const multi = this.redis.multi();
multi.incr(key);
multi.ttl(key);
const results = await multi.exec();
const count = (results?.[0]?.[1] as number) || 0;
const ttl = (results?.[1]?.[1] as number) || -1;
if (ttl === -1) {
await this.redis.expire(key, window);
}
return {
count,
reset: Date.now() + (ttl > 0 ? ttl : window) * 1000,
};
}
async reset(key: string) {
await this.redis.del(key);
}
}
const redis = new Redis(process.env.REDIS_URL!);
export const limiter = createRateLimiter({
limit: 100,
window: 60,
store: new RedisStore(redis),
});6. Next.js 中间件集成
在中间件层面统一限制:
import { createRateLimiter } from "@mono/rate-limit";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const limiter = createRateLimiter({ limit: 60, window: 60 });
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api")) {
const ip = request.headers.get("x-forwarded-for") || "127.0.0.1";
const result = await limiter.check(ip);
if (!result.success) {
return NextResponse.json(
{ error: "Rate limit exceeded" },
{ status: 429 },
);
}
}
return NextResponse.next();
}API 参考
createRateLimiter(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
limit | number | - | 窗口内最大请求数 |
window | number | - | 时间窗口(秒) |
prefix | string | "rl" | 键前缀 |
store | RateLimitStore | MemoryStore | 存储实现 |
RateLimitResult
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 是否允许请求 |
limit | number | 配置的限制数 |
remaining | number | 剩余请求数 |
reset | number | 窗口重置时间戳(ms) |
setRateLimitHeaders(headers, result)
设置标准 HTTP 速率限制响应头:X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset、Retry-After。
最佳实践
生产环境建议使用 Redis 存储,内存存储仅适用于单实例部署。
- 按端点分组 — 不同端点使用不同限制器和前缀
- 标识选择 — 未登录用 IP,已登录用用户 ID
- 错误信息 — 返回
reset时间让客户端知道何时重试 - 响应头 — 始终设置速率限制响应头
相关文档
- @mono/captcha — 验证码验证
- @mono/auth — 认证系统