Next.js Server Actions 实现无状态会话

313 阅读5分钟

Next.js 新增的 Server Actions 功能,可以让我们用更简单、安全的方式实现无状态会话,不需要写传统的 REST API,也不用依赖外部存储服务(如 Redis)。本文将用最基础的知识点,结合代码示例,帮你快速掌握如何用 Server Actions 和 Cookie 实现无状态会话。


什么是无状态会话?

无状态会话指的是服务器不保存用户的登录状态数据,所有状态信息都存储在客户端(比如 Cookie)里。服务器每次请求时,只通过客户端传来的信息判断用户身份。这样服务器不用维护会话数据,性能更好,扩展性更强。


什么是 Next.js Server Actions?

Server Actions 是 Next.js 13.4 及以后版本引入的一个新功能,允许你直接在 React 组件里调用服务器端的异步函数,省去了写 API 接口的麻烦。它本质上是用 POST 请求调用服务器函数,但你写起来就像调用本地函数一样简单。

  • Server Actions 用 "use server" 指令标记。
  • 支持在服务器组件和客户端组件中调用。
  • 支持表单的无刷新提交和渐进增强。
  • 参数和返回值必须可被 React 序列化。
  • 可以操作 Cookie、数据库等服务器资源。

为什么用 Server Actions 实现无状态会话?

传统会话依赖服务器内存或外部存储,难以扩展且增加复杂度。使用 Server Actions + Cookie 的方式:

  • 会话数据加密后存储在客户端 Cookie 中,安全且无状态。
  • 不需要额外的数据库或缓存系统。
  • 服务器负载更轻,部署更简单。

详细步骤与代码示例

1. 初始化项目

npx create-next-app@latest my-nextjs-app
cd my-nextjs-app

2. 创建会话管理库(session/index.ts)

"use server";

import { cookies } from 'next/headers';
import { encrypt, decrypt } from './encrypt'; // 加密模块,后面介绍

export type Session = {
  username: string;
};

// 从 Cookie 读取并解密会话
export const getSession = async (): Promise<Session | null> => {
  const cookieStore = cookies();
  const session = cookieStore.get('session');
  if (session?.value) {
    try {
      const decrypted = decrypt(session.value);
      return JSON.parse(decrypted) as Session;
    } catch {
      return null; // 解密失败视为无效会话
    }
  }
  return null;
};

// 加密并写入 Cookie
export const setSession = async (session: Session) => {
  const cookieStore = cookies();
  const encrypted = encrypt(JSON.stringify(session));
  cookieStore.set('session', encrypted);
};

// 删除 Cookie
export const removeSession = async () => {
  const cookieStore = cookies();
  cookieStore.delete('session');
};

3. 实现加密模块(session/encrypt.ts)

import { createCipheriv, createDecipheriv } from 'crypto';

// 这里使用 AES-256-CBC 算法,密钥和 IV 需要自己生成并保密
const key = Buffer.from('17204a84b538359abe8ba74807efa12a068c20a7c7f224b35198acf832cea57b', 'hex');
const iv = Buffer.from('da1cdcd9fe4199c835bd5f1d56446aff', 'hex');
const algorithm = 'aes-256-cbc';

// 加密函数
export const encrypt = (text: string) => {
  const cipher = createCipheriv(algorithm, key, iv);
  const encrypted = cipher.update(text, 'utf8', 'base64');
  return encrypted + cipher.final('base64');
};

// 解密函数
export const decrypt = (encrypted: string) => {
  const decipher = createDecipheriv(algorithm, key, iv);
  const decrypted = decipher.update(encrypted, 'base64', 'utf8');
  return decrypted + decipher.final('utf8');
};

提示:你可以用 crypto.randomBytes(32) 生成密钥,crypto.randomBytes(16) 生成 iv,确保安全。


4. 实现登录和登出 Server Actions(user/index.ts)

"use server";

import { setSession, removeSession } from '@/session';

export const signIn = async (username: string) => {
  await setSession({ username });
};

export const signOut = async () => {
  await removeSession();
};

5. 创建客户端登录组件(components/sign-in.tsx)

"use client";

import { signIn } from '@/user';
import { useState } from 'react';

const SignIn = () => {
  const [username, setUsername] = useState('');

  return (
    <div>
      <input
        type="text"
        placeholder="请输入用户名"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <button disabled={!username} onClick={() => signIn(username)}>
        登录
      </button>
    </div>
  );
};

export default SignIn;

6. 创建客户端登出组件(components/sign-out.tsx)

"use client";

import { signOut } from '@/user';

const SignOut = () => {
  return <button onClick={() => signOut()}>登出</button>;
};

export default SignOut;

7. 创建首页显示登录状态(app/page.tsx)

import { getSession } from '@/session';
import SignIn from '@/components/sign-in';
import SignOut from '@/components/sign-out';

export default async function Home() {
  const session = await getSession();

  return (
    <main style={{ padding: '20px', fontSize: '18px' }}>
      {session ? (
        <>
          <p>欢迎,{session.username}!你已登录。</p>
          <SignOut />
        </>
      ) : (
        <SignIn />
      )}
    </main>
  );
}

运行效果

  • 用户输入用户名点击登录,服务器端会话数据加密后写入 Cookie。
  • 页面刷新或跳转时,服务端读取 Cookie 并解密,显示用户登录状态。
  • 点击登出按钮,删除 Cookie,用户退出登录状态。

关键知识点总结

  • Server Actions:用 "use server" 标记的异步函数,自动生成服务器端接口,客户端调用就像调用本地函数。
  • 无状态会话:会话数据存储在客户端 Cookie 中,服务器不保存状态,适合无服务器环境。
  • Cookie 读写:使用 next/headers 中的 cookies() 读取和设置 Cookie。
  • 加密安全:使用 AES-256-CBC 对会话数据加密,防止被篡改和窃取。
  • 客户端组件与服务端组件:登录登出按钮用 "use client" 标记,支持交互;首页用服务端组件异步获取会话状态。

额外示例:用表单调用 Server Action

你也可以用 <form>action 属性调用 Server Action,实现无刷新表单提交:

// serverActions.ts
"use server";

export async function submitForm(data: FormData) {
  const username = data.get('username')?.toString() || '';
  // 处理登录逻辑
  await signIn(username);
}
// ClientForm.tsx
"use client";

import { submitForm } from '@/serverActions';

export default function ClientForm() {
  return (
    <form action={submitForm}>
      <input name="username" type="text" placeholder="用户名" />
      <button type="submit">登录</button>
    </form>
  );
}

这样,表单提交时会自动调用服务器端的 submitForm 函数,无需写额外的 API。


总结

通过 Next.js 的 Server Actions 和 Cookie 加密技术,我们可以轻松实现无状态会话,避免传统 REST API 的繁琐,提升开发效率和应用性能。以上示例代码清晰展示了从项目初始化到加密会话的完整流程,适合初学者快速上手。

想了解更多 Server Actions 的高级用法和实战技巧,可以参考 Next.js 官方文档和社区教程。祝你开发顺利!