多租户 / 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)
- 租户配置管理(主题、限制等)
- 自定义注册流程(邀请制)
- 账单系统与付费管理