feat: 切换 PostgreSQL 并实现软删除功能

- 数据库从 SQLite 切换到 PostgreSQL,添加 Docker Compose 配置
- 使用 dotenv-cli 支持 .env 和 .env.local 环境变量加载
- 使用 Prisma $extends 实现底层自动软删除机制
- 新增用户恢复和查询已删除用户的 API 接口
- 更新文档和类型定义
This commit is contained in:
Charile Zhou
2025-12-31 20:24:54 +08:00
parent b5624a664d
commit 3567aaff4d
14 changed files with 456 additions and 27 deletions

197
docs/backend/soft-delete.md Normal file
View File

@@ -0,0 +1,197 @@
# 软删除Soft Delete设计文档
## 概述
软删除是一种数据删除策略,不物理删除数据库记录,而是通过标记字段(`deletedAt`)来标识记录已被删除。这样可以保留数据历史,支持数据恢复,同时在业务层面表现为"已删除"。
## 设计目标
- **通用方案**:底层自动处理,业务代码无需关心软删除逻辑
- **可扩展**:新增模型只需加入配置数组即可启用软删除
- **支持恢复**:已删除的数据可以恢复
- **邮箱可复用**:用户软删除后,其邮箱可被新用户注册
## 技术实现
### 1. 数据库层 (Prisma Schema)
`apps/api/prisma/schema.prisma`:
```prisma
model User {
id String @id @default(cuid(2))
email String
password String
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // 软删除标记null 表示未删除
// 复合唯一约束:允许已删除用户的邮箱被重新注册
// email + null 唯一(未删除用户)
// email + deletedAt 唯一(已删除用户,同一邮箱可多次删除)
@@unique([email, deletedAt])
@@map("users")
}
```
### 2. PrismaService 扩展
`apps/api/src/prisma/prisma.service.ts`:
使用 Prisma Client Extensions (`$extends`) 实现底层自动软删除:
```typescript
// 启用软删除的模型列表
const SOFT_DELETE_MODELS: Prisma.ModelName[] = ['User'];
// 扩展拦截以下操作:
const softDeleteExtension = Prisma.defineExtension({
query: {
$allModels: {
// 查询操作:自动添加 deletedAt: null 条件
findMany({ model, args, query }) { ... },
findFirst({ model, args, query }) { ... },
findUnique({ model, args, query }) { ... },
count({ model, args, query }) { ... },
// 删除操作:转换为 update 设置 deletedAt
delete({ model, args }) { ... },
deleteMany({ model, args }) { ... },
},
},
});
```
### 3. 自动处理逻辑
| 操作 | 自动处理 |
|------|---------|
| `findMany` | 自动添加 `where: { deletedAt: null }` |
| `findFirst` | 自动添加 `where: { deletedAt: null }` |
| `findUnique` | 自动添加 `where: { deletedAt: null }` |
| `count` | 自动添加 `where: { deletedAt: null }` |
| `delete` | 转换为 `update({ data: { deletedAt: new Date() } })` |
| `deleteMany` | 转换为 `updateMany({ data: { deletedAt: new Date() } })` |
## 使用方式
### 业务代码(无需关心软删除)
```typescript
// 查询 - 自动过滤已删除记录
const users = await prisma.user.findMany();
// 删除 - 自动转换为软删除
await prisma.user.delete({ where: { id } });
// 按 ID 查找 - 自动过滤已删除记录
const user = await prisma.user.findUnique({ where: { id } });
```
### 查询已删除数据
显式指定 `deletedAt` 条件可绕过自动过滤:
```typescript
// 查询已删除的用户
const deletedUsers = await prisma.user.findMany({
where: { deletedAt: { not: null } },
});
// 查询指定 ID 的已删除用户
const deletedUser = await prisma.user.findFirst({
where: { id, deletedAt: { not: null } },
});
```
### 恢复已删除数据
```typescript
// 恢复用户
await prisma.user.update({
where: { id },
data: { deletedAt: null },
});
```
## API 接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/users` | GET | 获取用户列表(自动排除已删除) |
| `/users/:id` | GET | 获取单个用户(自动排除已删除) |
| `/users/:id` | DELETE | 软删除用户 |
| `/users/deleted` | GET | 获取已删除用户列表 |
| `/users/:id/restore` | PATCH | 恢复已删除用户 |
## 扩展新模型
为新模型启用软删除只需三步:
### 1. 修改 Schema
在模型中添加 `deletedAt` 字段:
```prisma
model Post {
id String @id @default(cuid(2))
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // 添加此字段
@@map("posts")
}
```
### 2. 配置启用
`apps/api/src/prisma/prisma.service.ts` 中添加模型名:
```typescript
const SOFT_DELETE_MODELS: Prisma.ModelName[] = ['User', 'Post'];
```
### 3. 添加恢复接口(可选)
如需恢复功能,在 Service 中添加:
```typescript
async findDeleted() {
return this.prisma.post.findMany({
where: { deletedAt: { not: null } },
});
}
async restore(id: string) {
const post = await this.prisma.post.findFirst({
where: { id, deletedAt: { not: null } },
});
if (!post) throw new NotFoundException('已删除的文章不存在');
return this.prisma.post.update({
where: { id },
data: { deletedAt: null },
});
}
```
## 注意事项
1. **复合唯一约束**`@@unique([email, deletedAt])` 使得:
- 未删除用户email + null 唯一
- 已删除用户email + deletedAt 唯一(同一邮箱可多次删除)
2. **update 操作不受影响**:可以更新已删除的记录,这对于恢复功能是必要的
3. **物理删除**:如确需物理删除数据,需要绕过扩展直接使用原始 PrismaClient
4. **性能考虑**:软删除会增加查询条件,建议为 `deletedAt` 字段添加索引
## 相关文件
- `apps/api/prisma/schema.prisma` - 数据库模型定义
- `apps/api/src/prisma/prisma.service.ts` - 软删除扩展实现
- `apps/api/src/user/user.service.ts` - 用户服务(含恢复逻辑)
- `apps/api/src/user/user.controller.ts` - 用户接口(含恢复接口)

View File

@@ -4,9 +4,10 @@
## 文档列表
| 文档 | 说明 | 适用场景 |
| ------------------------ | ------------ | -------------------------------- |
| [design.md](./design.md) | 项目设计文档 | 了解整体架构、技术选型、模块设计 |
| 文档 | 说明 | 适用场景 |
| ------------------------------------------ | ---------------- | -------------------------------- |
| [design.md](./design.md) | 项目设计文档 | 了解整体架构、技术选型、模块设计 |
| [backend/soft-delete.md](./backend/soft-delete.md) | 软删除设计文档 | 了解软删除机制、扩展新模型 |
## 快速链接