安全

接口限流

限制验证码、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/* 全局IP15 分钟1000代码内全局限制
ChatIP15 分钟20CHAT_RATE_LIMIT_MAX_REQUESTS
图片生成IP15 分钟20AI_IMAGE_RATE_LIMIT_MAX_REQUESTS
视频生成IP15 分钟10AI_VIDEO_RATE_LIMIT_MAX_REQUESTS
反馈提交IP / 匿名指纹15 分钟10FEEDBACK_RATE_LIMIT_MAX_REQUESTS
积分接口IP15 分钟60CREDITS_RATE_LIMIT_MAX_REQUESTS
文档搜索IP1 分钟120SEARCH_RATE_LIMIT_MAX_REQUESTS
签名上传链接用户15 分钟60UPLOAD_SIGNED_URL_RATE_LIMIT_MAX_REQUESTS
直接上传用户15 分钟20UPLOAD_DIRECT_RATE_LIMIT_MAX_REQUESTS

这些值是保守起点。真实产品上线后,应根据日志、账单和用户规模调整。

按什么对象限制

场景建议对象原因
未登录接口访问来源没有用户 ID,只能先按来源限制
登录用户 API用户 ID对公司网络、校园网这类共享 IP 更公平
短信验证码手机号 + 访问来源同一个手机号和同一个来源都要限制
邮件验证码邮箱 + 访问来源防止邮件轰炸,也防止批量试账号
AI 生成用户 ID + 套餐额度既保护账单,也能做付费差异
第三方 APIAPI 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: 窗口重置时间

相关链接