安全
接口限流
限制验证码、AI、搜索、上传等接口的调用次数,避免被刷费用和资源
这是什么
接口限流就是限制某个动作在一段时间内最多能做多少次。比如:同一个手机号 1 小时最多收 3 条短信,同一个用户 15 分钟最多生成 20 次 AI 内容,同一个 IP 1 分钟最多搜索 120 次。
它解决的不是“网站被大流量打挂”的 DDoS,而是更日常的滥用:短信费用被刷、AI 额度被刷、搜索接口被爬、上传入口被反复调用。
生产环境通常要有两层:
- Cloudflare 在边缘先挡住明显异常流量,减少服务器压力。
- 应用自己再根据用户、手机号、邮箱、AI 额度等业务身份做限制。
你先做哪几个
| 入口 | 为什么要限 | 建议 |
|---|---|---|
| 短信验证码 | 会直接产生短信费用,也会骚扰手机号 | 按手机号和来源限制发送次数 |
| 邮件验证码 / 密码找回 | 可能造成邮件轰炸和账号撞库 | 按邮箱和来源限制次数 |
| AI 聊天 / 生成 | 会消耗模型额度和队列资源 | 登录后使用,按用户和套餐限制 |
| 搜索接口 | 高频爬虫会拖慢数据库和搜索服务 | 单独设置较短窗口 |
| 文件上传 | 会消耗带宽、内存和对象存储 | 限制大小、类型和上传次数 |
| 公开反馈表单 | 后台和邮箱会被垃圾内容淹没 | 限流,加隐藏字段,异常时加验证 |
RATE_LIMIT_STORE=postgres 是什么
RATE_LIMIT_STORE 决定限流计数器存在哪里:
| 值 | 适合环境 | 特点 |
|---|---|---|
memory | 本地开发、单进程临时测试 | 计数存在进程内存里,重启就清零,多实例之间不共享 |
postgres | 生产环境 | 计数写入 rate_limit_bucket 表,多实例共享同一套计数 |
生产必须明确配置:
RATE_LIMIT_ENABLED=true
RATE_LIMIT_STORE=postgres简单理解:memory 只适合本地开发,生产要用 postgres。否则你以为“每 15 分钟最多 100 次”,实际在多实例、重启或扩容时可能不准。
当你显式配置 RATE_LIMIT_STORE=postgres 时,PostgreSQL 不可用应视为配置错误,而不是静默退回 memory。否则生产限流会在你最需要它时失效。
当前内置限制
| 入口 | 默认维度 | 默认窗口 | 默认阈值 | 环境变量 |
|---|---|---|---|---|
/api/* 全局 | IP | 15 分钟 | 1000 | 代码内全局限制 |
| Chat | IP | 15 分钟 | 20 | CHAT_RATE_LIMIT_MAX_REQUESTS |
| 图片生成 | IP | 15 分钟 | 20 | AI_IMAGE_RATE_LIMIT_MAX_REQUESTS |
| 视频生成 | IP | 15 分钟 | 10 | AI_VIDEO_RATE_LIMIT_MAX_REQUESTS |
| 反馈提交 | IP / 匿名指纹 | 15 分钟 | 10 | FEEDBACK_RATE_LIMIT_MAX_REQUESTS |
| 积分接口 | IP | 15 分钟 | 60 | CREDITS_RATE_LIMIT_MAX_REQUESTS |
| 文档搜索 | IP | 1 分钟 | 120 | SEARCH_RATE_LIMIT_MAX_REQUESTS |
| 签名上传链接 | 用户 | 15 分钟 | 60 | UPLOAD_SIGNED_URL_RATE_LIMIT_MAX_REQUESTS |
| 直接上传 | 用户 | 15 分钟 | 20 | UPLOAD_DIRECT_RATE_LIMIT_MAX_REQUESTS |
这些值是保守起点。真实产品上线后,应根据日志、账单和用户规模调整。
按什么对象限制
| 场景 | 建议对象 | 原因 |
|---|---|---|
| 未登录接口 | 访问来源 | 没有用户 ID,只能先按来源限制 |
| 登录用户 API | 用户 ID | 对公司网络、校园网这类共享 IP 更公平 |
| 短信验证码 | 手机号 + 访问来源 | 同一个手机号和同一个来源都要限制 |
| 邮件验证码 | 邮箱 + 访问来源 | 防止邮件轰炸,也防止批量试账号 |
| AI 生成 | 用户 ID + 套餐额度 | 既保护账单,也能做付费差异 |
| 第三方 API | API key | 每个集成方应该有独立额度 |
| 上传 | 用户 ID + 文件大小 | 上传成本和用户身份强相关 |
常见后果
更完整的案例放在 常见风险。如果你只想先理解限流的价值,可以记住这几类后果:
- 短信验证码被刷,费用上涨,手机号被骚扰。
- AI 接口被刷,余额用完,正常用户无法生成。
- 搜索接口被爬,数据库和搜索服务变慢。
- 反馈表单被刷,真正的用户反馈被垃圾内容淹没。
- 上传入口被滥用,对象存储和带宽费用上涨。
基本用法
import { createRateLimiter, setRateLimitHeaders } from "@01mvp/rate-limit";
const limiter = createRateLimiter({
limit: 100,
window: 60,
prefix: "api",
});
const result = await limiter.check(clientIp);
setRateLimitHeaders(response.headers, result);
if (!result.success) {
return new Response("Too Many Requests", { status: 429 });
}返回的 RateLimitResult 包含:
success: 是否放行limit: 最大请求数remaining: 剩余可用次数reset: 窗口重置时间