day008 的核心问题是:上一节的登录状态只存在于 Session Cookie 中,虽然 session.user 能拿到 GitHub 用户信息,但数据库里没有一个稳定的用户实体。
开始学习之前让我们带着这几个问题一起往下看:
-
为什么不能只建一张 User 表,还要额外建 Account 和 Session?
-
为什么
sessionToken要唯一? -
为什么
expires是DateTime,而 Account 的expires_at是Int? -
为什么 Session 也需要
userId外键?
这一节要引入:
markdown
GitHub OAuth
↓
NextAuth
↓
Prisma Adapter
↓
PostgreSQL
先把几层关系重新捋一遍
arduino
PostgreSQL
↓
真正存储数据的数据库服务
Prisma Schema
↓
用 Prisma 描述数据库表结构的文件
Prisma Client
↓
根据 Schema 生成的 TypeScript 数据库操作 API
src/db/db.ts 中的 db
↓
Prisma Client 的一个实例
PrismaAdapter(db)
↓
把这个 Prisma Client 交给 NextAuth 使用
src/db/db.ts:
arduino
export const db = globalForPrisma.prisma ?? createPrismaClient()
这个 db 可以理解成:
arduino
一个已经连接 PostgreSQL、可以执行查询的 Prisma Client 对象
例如项目中的代码:
scss
db.doc.findMany()
db.doc.create(...)
db.doc.update(...)
这些都不是直接调用 PostgreSQL,而是调用 Prisma Client。Prisma Client 在底层把它们转换成 SQL,再通过 PostgreSQL 驱动执行。
Prisma 到底是什么
Prisma 不是 PostgreSQL,也不是数据库本身。
它是一套 Node.js / TypeScript 数据库工具,主要包括:
arduino
Prisma Schema
→ 定义模型
Prisma Client
→ 在 TypeScript 中操作数据
Prisma CLI
→ generate、db push、migrate 等命令
例如:
kotlin
model Doc {
id Int @id @default(autoincrement())
title String
content String
}
执行:
arduino
npm run db:push
大致过程是:
bash
读取 prisma/schema.prisma
↓
连接 DATABASE_URL 指向的 PostgreSQL
↓
创建或调整数据库中的表结构
执行:
arduino
npm run db:generate
则是:
arduino
读取 prisma/schema.prisma
↓
生成 TypeScript Prisma Client
所以:
perl
db push → 同步数据库表结构
db generate → 生成代码 API
这两个动作不是一回事。
NextAuth 是什么
NextAuth 也是一个 npm 包,但它解决的是另一个领域:
NextAuth
→ 登录、OAuth、Session、Cookie、Provider、授权流程
它不是数据库工具,也不是 GitHub 本身。
可以这样分工:
arduino
GitHub
→ 外部身份提供商
NextAuth
→ 认证流程管理器
Prisma Adapter
→ NextAuth 与 Prisma Client 之间的翻译层
Prisma Client
→ TypeScript 数据库操作 API
PostgreSQL
→ 最终存储数据
完整关系:
scss
GitHub OAuth
↓
NextAuth
↓
PrismaAdapter(db)
↓
Prisma Client
↓
PostgreSQL
Adapter 是什么
Adapter 可以理解成 NextAuth 和数据库之间的"翻译层"。
NextAuth 需要完成这些操作:
查找用户
创建用户
保存 GitHub 账号关联
创建 Session
查询 Session
删除 Session
保存邮箱验证码
但 NextAuth 不知道你使用 PostgreSQL、MySQL 还是 MongoDB,也不知道你的 ORM 是 Prisma 还是 Drizzle。
所以它定义了一套 Adapter 接口:
arduino
NextAuth
↓ 调用统一的 Adapter 方法
Prisma Adapter
↓ 翻译成 Prisma API
Prisma Client
↓
PostgreSQL
例如,用户第一次用 GitHub 登录时,NextAuth 可能需要:
scss
adapter.getUserByAccount(...)
adapter.createUser(...)
adapter.linkAccount(...)
adapter.createSession(...)
@auth/prisma-adapter 负责把这些操作翻译成 Prisma 查询。
它不是把整个 session.user 随便存进数据库,而是按照认证领域的数据模型拆成多张表。
Adapter 为什么需要 db
NextAuth 只知道自己需要调用一些"存储用户"的操作,例如:
createUser
getUser
getUserByAccount
linkAccount
createSession
getSessionAndUser
deleteSession
但它不知道你项目使用什么数据库。
PrismaAdapter(db) 做的事情就是告诉 NextAuth:
arduino
如果你需要 createUser,
我会用 Prisma Client 的方式帮你执行。
如果你需要 getUserByAccount,
我也会把它翻译成 Prisma 查询。
所以它不是"Adapter 解释 db 给 Prisma",而是:
Adapter 使用你提供的 Prisma Client,把 NextAuth 的标准存储操作翻译成 Prisma 查询。
例如概念上可能类似:
vbnet
NextAuth:
createUser(user)
PrismaAdapter:
db.user.create({ data: user })
Prisma:
INSERT INTO "User" ...
PostgreSQL:
真正写入记录
为什么需要多张表
标准 Prisma Adapter 通常需要这些模型:
sql
User
用户的基本身份信息
Account
用户和 GitHub 等登录平台的关联
Session
当前登录会话
VerificationToken
邮箱登录时的一次性验证码或验证链接
关系大致是:
sql
User 1 ─── N Account
User 1 ─── N Session
同一个用户未来可能绑定多个登录方式:
sql
一个 User
├── GitHub Account
├── 微信 Account
└── 邮箱登录记录
所以不能只把 GitHub 返回的用户对象直接塞进一个表里。
登录后的数据库变化
第一次 GitHub 登录,数据库大致会发生:
markdown
1. 查询 Account:这个 GitHub 账号以前登录过吗?
2. 没有找到:
- 创建 User
- 创建 Account,记录 GitHub 账号关联
3. 创建 Session
4. 浏览器仍然拿到 Session Cookie
再次登录时通常是:
markdown
1. 根据 GitHub provider 和 providerAccountId 找到 Account
2. 通过 Account 找到 User
3. 创建或更新 Session
4. 不重复创建 User
这里要区分:
sql
GitHub Access Token
→ 第三方授权凭证
User
→ 你的系统中的用户实体
Session
→ 你的系统中的一次登录会话
现在你能试着回答文章一开始的几个问题了吧?