多租户 / SaaS 应用架构设计实战(Next.js + Prisma + Auth.js)

213 阅读2分钟

多租户 / SaaS 应用架构设计实战(Next.js + Prisma + Auth.js)


1. 项目核心技术栈

技术作用
Next.js前端与服务端渲染
Prisma类型安全 ORM,操作数据库
Auth.js (NextAuth)用户认证与授权
PostgreSQL/MySQL支持多租户数据库
Tailwind CSS快速 UI 开发(可选)

2. 多租户设计模式

2.1 单库多租户(推荐)

  • 一个数据库,多套数据表通过 tenantId 进行隔离
  • 简单易维护,适合中小 SaaS

2.2 多库多租户

  • 每个租户独立数据库实例
  • 隔离性好,维护成本高,适合大型客户

3. 数据库设计示例(单库多租户)

model Tenant {
  id        Int      @id @default(autoincrement())
  name      String
  users     User[]
  createdAt DateTime @default(now())
}

model User {
  id         Int      @id @default(autoincrement())
  email      String   @unique
  password   String
  tenantId   Int
  tenant     Tenant   @relation(fields: [tenantId], references: [id])
  role       Role     @default(USER)
  createdAt  DateTime @default(now())
}

enum Role {
  USER
  ADMIN
}

数据隔离通过 tenantId 过滤。


4. Auth.js 集成(多租户支持)

4.1 安装依赖

npm install next-auth @next-auth/prisma-adapter

4.2 配置 NextAuth

// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import prisma from '../../../lib/prisma';

export default NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
        tenantId: { label: 'Tenant ID', type: 'text' },
      },
      async authorize(credentials) {
        const user = await prisma.user.findUnique({
          where: { email: credentials.email },
          include: { tenant: true },
        });
        if (user && user.password === credentials.password && user.tenantId === Number(credentials.tenantId)) {
          return user;
        }
        return null;
      },
    }),
  ],
  callbacks: {
    async session({ session, user }) {
      session.user.id = user.id;
      session.user.tenantId = user.tenantId;
      session.user.role = user.role;
      return session;
    },
  },
  session: {
    strategy: 'jwt',
  },
});

5. 请求层中租户隔离

在所有需要访问数据库的地方,必须添加 tenantId 过滤。

const posts = await prisma.post.findMany({
  where: { tenantId: session.user.tenantId },
});

6. 中间件实现租户鉴权与路由隔离

// middleware.ts
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';

export async function middleware(req) {
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  const { pathname } = req.nextUrl;

  if (!token && pathname.startsWith('/app')) {
    return NextResponse.redirect(new URL('/auth/signin', req.url));
  }

  if (token && pathname.startsWith('/app')) {
    // 可根据 token 里 tenantId 实现租户访问控制
    return NextResponse.next();
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/app/:path*'],
};

7. 项目结构建议

/pages
  /auth
    signin.tsx
  /app
    /dashboard
      index.tsx
/lib
  prisma.ts           // Prisma 实例
  auth.ts             // 认证相关辅助函数
/components
  UI 组件
/middleware.ts

8. 安全与性能建议

  • 严控 tenantId 传递,避免越权
  • 在 API 层强制租户隔离条件
  • 结合中间件限制路由访问
  • 数据库索引优化 tenantId 字段
  • 定期审计日志

9. 扩展功能建议

  • 多角色权限管理(RBAC)
  • 租户配置管理(主题、限制等)
  • 自定义注册流程(邀请制)
  • 账单系统与付费管理