library-picturebook-activity/.cursor/rules/database-design.mdc

279 lines
5.7 KiB
Plaintext
Raw Normal View History

---
description: Prisma 数据库设计规范和最佳实践
globs:
- "backend/prisma/**/*.prisma"
alwaysApply: false
---
# 数据库设计规范
## Prisma Schema 规范
### 表结构要求
所有业务表必须包含以下字段:
```prisma
model YourModel {
id Int @id @default(autoincrement())
tenantId Int @map("tenant_id") /// 租户ID必填
// 业务字段...
name String
description String?
// 审计字段
validState Int @default(1) @map("valid_state") /// 1-有效2-失效
creator Int? /// 创建人ID
modifier Int? /// 修改人ID
createTime DateTime @default(now()) @map("create_time")
modifyTime DateTime @updatedAt @map("modify_time")
// 关系
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
creatorUser User? @relation("YourModelCreator", fields: [creator], references: [id], onDelete: SetNull)
modifierUser User? @relation("YourModelModifier", fields: [modifier], references: [id], onDelete: SetNull)
@@map("your_table_name")
}
```
### 字段命名规范
- Prisma 模型使用 camelCase`tenantId`, `createTime`
- 数据库列使用 snake_case`tenant_id`, `create_time`
- 使用 `@map()` 映射字段名
- 使用 `@@map()` 映射表名
### 状态字段
使用 `validState` 表示数据有效性:
- `1` - 有效
- `2` - 失效(软删除)
```prisma
validState Int @default(1) @map("valid_state")
```
## 关系设计
### 一对多关系
```prisma
model Tenant {
id Int @id @default(autoincrement())
users User[]
}
model User {
id Int @id @default(autoincrement())
tenantId Int @map("tenant_id")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
}
```
### 多对多关系
使用显式中间表:
```prisma
model User {
id Int @id @default(autoincrement())
roles UserRole[]
}
model Role {
id Int @id @default(autoincrement())
users UserRole[]
}
model UserRole {
userId Int @map("user_id")
roleId Int @map("role_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
@@id([userId, roleId])
@@map("user_roles")
}
```
### 一对一关系
```prisma
model User {
id Int @id @default(autoincrement())
teacher Teacher?
}
model Teacher {
id Int @id @default(autoincrement())
userId Int @unique @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
### 级联删除规则
- 强依赖关系:`onDelete: Cascade`
- 弱依赖关系:`onDelete: SetNull`(字段必须可选)
- 保护性关系:`onDelete: Restrict`
## 索引设计
### 自动索引
- 主键自动创建索引
- 外键字段自动创建索引
- `@unique` 字段自动创建唯一索引
### 复合索引
```prisma
model User {
tenantId Int
username String
@@unique([tenantId, username])
@@index([tenantId, validState])
}
```
### 性能优化索引
为频繁查询的字段添加索引:
```prisma
model Contest {
tenantId Int
status Int
startTime DateTime
@@index([tenantId, status])
@@index([tenantId, startTime])
}
```
## Prisma 查询最佳实践
### 使用 include 预加载关联
避免 N+1 查询问题:
```typescript
// ✅ 好的做法 - 使用 include 预加载
const users = await prisma.user.findMany({
where: { tenantId },
include: {
roles: {
include: {
role: true,
},
},
},
});
// ❌ 不好的做法 - N+1 查询
const users = await prisma.user.findMany({
where: { tenantId },
});
for (const user of users) {
user.roles = await prisma.userRole.findMany({
where: { userId: user.id },
});
}
```
### 使用 select 精简字段
只查询需要的字段:
```typescript
const users = await prisma.user.findMany({
where: { tenantId },
select: {
id: true,
username: true,
nickname: true,
// 不查询 password 等敏感字段
},
});
```
### 分页查询
```typescript
const users = await prisma.user.findMany({
where: { tenantId, validState: 1 },
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createTime: "desc" },
});
const total = await prisma.user.count({
where: { tenantId, validState: 1 },
});
```
### 事务处理
使用 `$transaction` 确保数据一致性:
```typescript
await prisma.$transaction(async (tx) => {
// 创建用户
const user = await tx.user.create({
data: {
username: "test",
tenantId,
},
});
// 创建用户角色关联
await tx.userRole.createMany({
data: roleIds.map((roleId) => ({
userId: user.id,
roleId,
})),
});
});
```
## 数据迁移
### 创建迁移
```bash
# 开发环境 - 创建并应用迁移
pnpm prisma:migrate:dev
# 生产环境 - 只应用迁移
pnpm prisma:migrate:deploy
```
### 迁移命名
使用描述性的迁移名称:
```bash
prisma migrate dev --name add_contest_module
prisma migrate dev --name add_user_avatar_field
```
### 迁移注意事项
- 迁移前备份数据库
- 测试迁移在开发环境的执行
- 生产环境使用 `migrate deploy` 而不是 `migrate dev`
- 不要手动修改已应用的迁移文件
## 性能优化清单
- [ ] 频繁查询的字段添加了索引
- [ ] 使用 `include` 预加载关联数据
- [ ] 使用 `select` 只查询需要的字段
- [ ] 实现了分页查询
- [ ] 复杂操作使用了事务
- [ ] 避免了 N+1 查询问题