154 lines
4.0 KiB
Markdown
154 lines
4.0 KiB
Markdown
|
|
# iOS 登录流程 —— 数据库设计
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、users 表
|
|||
|
|
|
|||
|
|
用户主表,存储用户基础信息。
|
|||
|
|
|
|||
|
|
```prisma
|
|||
|
|
model User {
|
|||
|
|
id String @id @default(cuid())
|
|||
|
|
email String?
|
|||
|
|
nickname String?
|
|||
|
|
avatarUrl String?
|
|||
|
|
role UserRole @default(USER)
|
|||
|
|
status UserStatus @default(ACTIVE)
|
|||
|
|
onboardingCompleted Boolean @default(false)
|
|||
|
|
|
|||
|
|
authAccounts AuthAccount[]
|
|||
|
|
refreshTokens RefreshToken[]
|
|||
|
|
|
|||
|
|
createdAt DateTime @default(now())
|
|||
|
|
updatedAt DateTime @updatedAt
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum UserRole {
|
|||
|
|
USER
|
|||
|
|
ADMIN
|
|||
|
|
SUPER_ADMIN
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum UserStatus {
|
|||
|
|
ACTIVE
|
|||
|
|
DISABLED
|
|||
|
|
DELETED
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | String (cuid) | 主键 |
|
|||
|
|
| `email` | String? | 邮箱,可选(Apple 登录首次可能提供) |
|
|||
|
|
| `nickname` | String? | 昵称 |
|
|||
|
|
| `avatarUrl` | String? | 头像 URL |
|
|||
|
|
| `role` | UserRole | 角色,默认 USER |
|
|||
|
|
| `status` | UserStatus | 状态,默认 ACTIVE |
|
|||
|
|
| `onboardingCompleted` | Boolean | 是否完成引导,默认 false |
|
|||
|
|
| `authAccounts` | 关联 | 一对多关联 auth_accounts |
|
|||
|
|
| `refreshTokens` | 关联 | 一对多关联 refresh_tokens |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、auth_accounts 表
|
|||
|
|
|
|||
|
|
记录用户通过什么方式(provider)登录,支持一个用户绑定多个登录方式。
|
|||
|
|
|
|||
|
|
```prisma
|
|||
|
|
model AuthAccount {
|
|||
|
|
id String @id @default(cuid())
|
|||
|
|
userId String
|
|||
|
|
provider AuthProvider
|
|||
|
|
providerUserId String
|
|||
|
|
email String?
|
|||
|
|
|
|||
|
|
user User @relation(fields: [userId], references: [id])
|
|||
|
|
|
|||
|
|
createdAt DateTime @default(now())
|
|||
|
|
updatedAt DateTime @updatedAt
|
|||
|
|
|
|||
|
|
@@unique([provider, providerUserId])
|
|||
|
|
@@index([userId])
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum AuthProvider {
|
|||
|
|
DEV
|
|||
|
|
APPLE
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | String (cuid) | 主键 |
|
|||
|
|
| `userId` | String | 关联 users 表 |
|
|||
|
|
| `provider` | AuthProvider | 登录提供商(DEV / APPLE) |
|
|||
|
|
| `providerUserId` | String | 提供商侧的用户唯一 ID |
|
|||
|
|
| `email` | String? | 提供商侧邮箱 |
|
|||
|
|
| `@@unique([provider, providerUserId])` | 约束 | 同一个提供商的用户唯一 |
|
|||
|
|
|
|||
|
|
**查找逻辑**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Apple 登录时:
|
|||
|
|
provider = APPLE
|
|||
|
|
providerUserId = identityToken 里校验出来的 sub
|
|||
|
|
→ 如果不存在,创建 User + AuthAccount
|
|||
|
|
→ 如果存在,直接找到对应 User
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、refresh_tokens 表
|
|||
|
|
|
|||
|
|
**重要:refreshToken 不要明文存数据库,只存 hash。**
|
|||
|
|
|
|||
|
|
```prisma
|
|||
|
|
model RefreshToken {
|
|||
|
|
id String @id @default(cuid())
|
|||
|
|
userId String
|
|||
|
|
tokenHash String
|
|||
|
|
expiresAt DateTime
|
|||
|
|
revokedAt DateTime?
|
|||
|
|
|
|||
|
|
user User @relation(fields: [userId], references: [id])
|
|||
|
|
|
|||
|
|
createdAt DateTime @default(now())
|
|||
|
|
updatedAt DateTime @updatedAt
|
|||
|
|
|
|||
|
|
@@index([userId])
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | String (cuid) | 主键,同时写入 JWT payload 作为 `tokenId` |
|
|||
|
|
| `userId` | String | 关联 users 表 |
|
|||
|
|
| `tokenHash` | String | refreshToken 的 hash 值(SHA-256) |
|
|||
|
|
| `expiresAt` | DateTime | 过期时间 |
|
|||
|
|
| `revokedAt` | DateTime? | 撤销时间(登出时设置) |
|
|||
|
|
|
|||
|
|
**刷新时的校验链**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 解析 refreshToken JWT,拿到 userId + tokenId
|
|||
|
|
2. 查 refresh_tokens 表,找到对应记录
|
|||
|
|
3. 对比 tokenHash
|
|||
|
|
4. 确认 revokedAt 为 null(未撤销)
|
|||
|
|
5. 确认 expiresAt 未过期
|
|||
|
|
6. 签发新的 accessToken(可选轮换新的 refreshToken)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**登出时**:将对应记录的 `revokedAt` 设为当前时间。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、ER 关系总结
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
User (1) ──── (N) AuthAccount 一个用户可有多种登录方式
|
|||
|
|
User (1) ──── (N) RefreshToken 一个用户可有多个活跃 refreshToken(多设备)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 用户与登录方式是解耦的:用户是一个独立实体,通过 `auth_accounts` 关联到具体的第三方身份。
|
|||
|
|
- 这种设计天然支持未来扩展更多登录方式(如 Google、微信等),只需在 `AuthProvider` 枚举中添加即可。
|