---
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 查询问题
