You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 11:47:14