如何在Next.js 14 Server Actions中基于IP实现Upstash限流
如何在Server Actions中基于用户IP地址实现限流?
我特意写了一段有问题的代码来演示当前遇到的困境:在Server Actions里没法直接拿到request对象,也就获取不到用户IP,没法做基于IP的限流。之前找了不少方案都没解决,毕竟Server Actions是比较新的特性,相关讨论不多。
错误代码示例
"use server" // NEXTJS IMPORTS import { auth } from "@/auth"; // CONFIG import { API_ERROR_MESSAGES } from "@/app/config"; // ACTIONS import { getUserIdByUsername } from "@/app/actions/add_post/getUserIdByUsername"; // REDIS import { getCache, setCache } from "@/app/actions/redis/redisFunctions"; import { Redis } from '@upstash/redis'; import { Ratelimit } from "@/upstash/ratelimit"; // DATABASE import { database } from "@/app/lib/database"; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(1, "10 s") }) export async function createMentions(description: string, postId: number, commentId?: number) { // 问题出在这:Server Actions里没有直接的request对象,这么写会报错 const ip = request.headers.get("x-forwarded-for") ?? ""; const { success, reset } = await ratelimit.limit(ip); if(!success) { const now = Date.now(); const retryAfter = Math.floor((reset - now) / 1000); /* 如果是普通API端点,用NextResponse可以这么返回限流信息: return new NextResponse("Too many requests", { status: 429, headers: { ["retry-after"]: `${retryAfter}`, }, }); */ return { success: false, error: { code: "TOO_MANY_REQUESTS" }} // 这是我想要的返回格式 } return { success: true, error: { code: "SOME_SUCCESS_MESSAGE" }} }
解决办法:用Next.js的headers()函数拿IP
Next.js 13+的Server Actions里,可以通过next/headers提供的headers()函数获取请求头,从而拿到用户IP。
修正后的完整代码
"use server" // NEXTJS IMPORTS import { auth } from "@/auth"; import { headers } from "next/headers"; // 新增:导入headers函数 // CONFIG import { API_ERROR_MESSAGES } from "@/app/config"; // ACTIONS import { getUserIdByUsername } from "@/app/actions/add_post/getUserIdByUsername"; // REDIS import { getCache, setCache } from "@/app/actions/redis/redisFunctions"; import { Redis } from '@upstash/redis'; import { Ratelimit } from "@/upstash/ratelimit"; // DATABASE import { database } from "@/app/lib/database"; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(1, "10 s") }) export async function createMentions(description: string, postId: number, commentId?: number) { // 获取请求头集合 const headerList = headers(); // 处理IP:代理场景下优先取x-forwarded-for的第一个IP,否则取x-real-ip,本地开发用localhost兜底 const ip = headerList.get("x-forwarded-for")?.split(",")[0].trim() || headerList.get("x-real-ip") || "localhost"; const { success, reset } = await ratelimit.limit(ip); if(!success) { const now = Date.now(); const retryAfter = Math.floor((reset - now) / 1000); // 把重试时间也返回给前端,方便提示用户 return { success: false, error: { code: "TOO_MANY_REQUESTS", retryAfter }} } // 这里替换成你的实际业务逻辑,比如创建提及记录到数据库 // await database.mention.create({ // data: { /* 你的数据 */ } // }) return { success: true, message: "提及创建成功" } }
额外注意点
x-forwarded-for可能返回多个用逗号分隔的IP,第一个才是用户真实IP,所以要做拆分和去空格处理。- 生产环境要确认托管平台(比如Vercel、阿里云)是否正确传递了
x-forwarded-for或x-real-ip请求头,不然会拿不到正确IP。 - 本地开发时这些头通常不存在,用
localhost兜底可以避免限流逻辑异常。
内容的提问来源于stack exchange,提问作者PiToN
相关产品推荐
相关产品推荐

