223 lines
6.4 KiB
Markdown
223 lines
6.4 KiB
Markdown
# 删除规范
|
||
|
||
## 概述
|
||
|
||
统一使用软删除机制,通过 `is_delete` 字段标记记录状态,不物理删除数据。删除操作支持逗号分隔的多 ID 批量删除。
|
||
|
||
:::tip 软删除约定
|
||
- `is_delete = False`(0):正常记录(默认值)
|
||
- `is_delete = True`(1):已删除记录
|
||
- 所有查询方法自动过滤 `is_delete=False`,业务层无需关心
|
||
:::
|
||
|
||
## 软删除 vs 硬删除
|
||
|
||
| 维度 | 软删除 | 硬删除 |
|
||
|------|--------|--------|
|
||
| 实现方式 | 更新 `is_delete=True` | `DELETE FROM table` |
|
||
| 数据可恢复 | ✅ 可恢复 | ❌ 不可恢复 |
|
||
| 查询影响 | 需加 `is_delete=False` | 数据已不存在 |
|
||
| 存储空间 | 占用(数据仍在表中) | 释放 |
|
||
| 适用场景 | 业务数据(用户、订单等) | 日志、临时数据 |
|
||
| 项目约定 | ✅ 统一使用 | ❌ 禁止使用 |
|
||
|
||
## BaseModel 公共字段
|
||
|
||
所有业务模型继承 `BaseModel`,自动包含 `is_delete` 字段:
|
||
|
||
```python
|
||
# application/models.py
|
||
class BaseModel(models.Model):
|
||
"""基础模型类"""
|
||
class Meta:
|
||
abstract = True
|
||
|
||
id = models.AutoField(primary_key=True)
|
||
create_user = models.CharField(max_length=50, null=True, blank=True)
|
||
create_time = models.DateTimeField(auto_now_add=True)
|
||
update_user = models.CharField(max_length=50, null=True, blank=True)
|
||
update_time = models.DateTimeField(auto_now=True)
|
||
is_delete = models.IntegerField(default=0, db_comment='是否删除:0-正常 1-已删除')
|
||
```
|
||
|
||
## 删除接口
|
||
|
||
### URL 格式
|
||
|
||
```
|
||
DELETE /{module}/delete/{id}
|
||
```
|
||
|
||
支持逗号分隔的多 ID 删除:
|
||
|
||
```
|
||
DELETE /example/delete/1,2,3
|
||
```
|
||
|
||
### View 示例
|
||
|
||
```python
|
||
@method_decorator(check_login, name="delete")
|
||
class ExampleDeleteView(PermissionRequired, View):
|
||
"""删除案例视图"""
|
||
permission_required = ('sys:example:delete',)
|
||
|
||
@operation_log(title="案例管理-删除记录", log_type=LogType.DELETE)
|
||
def delete(self, request, id):
|
||
# 演示环境禁止操作
|
||
if DJANGO_DEMO:
|
||
return R.failed("演示环境,暂无操作权限")
|
||
|
||
result = services.delete_examples(id)
|
||
return result
|
||
```
|
||
|
||
## Service 实现
|
||
|
||
```python
|
||
def delete_examples(instance_ids):
|
||
"""删除案例(批量删除)"""
|
||
if not instance_ids:
|
||
return R.failed("记录ID不存在")
|
||
|
||
try:
|
||
# 解析ID列表
|
||
id_list = [int(id_str.strip()) for id_str in instance_ids.split(',') if id_str.strip()]
|
||
if not id_list:
|
||
return R.failed("无效的记录ID")
|
||
|
||
# 批量更新删除标识(逻辑删除)
|
||
updated_count = Example.objects.filter(
|
||
id__in=id_list,
|
||
is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
return R.ok(msg=f"本次共删除{updated_count}条数据")
|
||
except ValueError:
|
||
return R.failed("记录ID格式错误")
|
||
except Exception as e:
|
||
logging.error(f"删除案例异常,IDs: {instance_ids}, 错误: {str(e)}")
|
||
return R.failed(msg="删除失败,请稍后重试")
|
||
```
|
||
|
||
:::tip 返回值说明
|
||
`delete` 方法返回实际软删除的条数。如果传入的 ID 中包含已删除或不存在的记录,返回值会小于入参数量。
|
||
:::
|
||
|
||
## 删除流程
|
||
|
||
```
|
||
1. View 接收请求(id 参数,逗号分隔)
|
||
↓
|
||
2. 演示模式检查(DJANGO_DEMO)
|
||
↓(拦截则返回 R.failed)
|
||
3. Service.delete_examples(id)
|
||
↓
|
||
4. 解析 ID 列表(逗号分隔转 list)
|
||
↓
|
||
5. ORM 批量更新 is_delete=True
|
||
↓
|
||
6. 返回 R.ok(msg="本次共删除N条数据")
|
||
```
|
||
|
||
## 查询过滤
|
||
|
||
所有查询方法必须过滤 `is_delete=False`:
|
||
|
||
```python
|
||
# 分页查询
|
||
query = Example.objects.filter(is_delete=False)
|
||
|
||
# 详情查询
|
||
instance = Example.objects.filter(is_delete=False, id=id).first()
|
||
|
||
# 列表查询
|
||
queryset = Example.objects.filter(is_delete=False, status=1)
|
||
|
||
# 带筛选条件的查询
|
||
query = Example.objects.filter(is_delete=False, name__contains=name)
|
||
```
|
||
|
||
:::warning 常见错误
|
||
忘记加 `is_delete=False` 是最常见的 bug,会导致已删除的数据重新出现在列表中。建议在代码审查时重点检查。
|
||
:::
|
||
|
||
## 级联删除处理
|
||
|
||
当模块之间存在关联关系时,删除父记录需要同步处理子记录:
|
||
|
||
```python
|
||
def delete_role(role_ids):
|
||
"""删除角色(同步清理关联数据)"""
|
||
id_list = [int(id_str.strip()) for id_str in role_ids.split(',')]
|
||
|
||
# 1. 软删除角色
|
||
updated_count = Role.objects.filter(
|
||
id__in=id_list, is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
# 2. 同步软删除角色菜单关联
|
||
RoleMenu.objects.filter(
|
||
role_id__in=id_list, is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
# 3. 同步软删除用户角色关联
|
||
UserRole.objects.filter(
|
||
role_id__in=id_list, is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
return R.ok(msg=f"本次共删除{updated_count}条数据")
|
||
```
|
||
|
||
## 数据恢复
|
||
|
||
软删除的数据可以通过恢复接口重新启用:
|
||
|
||
```python
|
||
def restore_examples(instance_ids):
|
||
"""恢复已删除的案例"""
|
||
id_list = [int(id_str.strip()) for id_str in instance_ids.split(',')]
|
||
|
||
updated_count = Example.objects.filter(
|
||
id__in=id_list, is_delete=True
|
||
).update(is_delete=False)
|
||
|
||
return R.ok(msg=f"本次共恢复{updated_count}条数据")
|
||
```
|
||
|
||
:::tip 恢复场景
|
||
数据恢复通常用于:
|
||
1. 误删除后的数据恢复
|
||
2. 回收站功能
|
||
3. 数据审计
|
||
:::
|
||
|
||
## 权限标识
|
||
|
||
| 操作 | 权限标识 | 说明 |
|
||
|------|---------|------|
|
||
| 删除 | `sys:{module}:delete` | 删除记录(支持批量) |
|
||
|
||
## 常见错误提示
|
||
|
||
| 提示 | 原因 |
|
||
|------|------|
|
||
| "记录ID不存在" | 入参为空 |
|
||
| "无效的记录ID" | ID 格式错误或全部无效 |
|
||
| "记录ID格式错误" | ID 包含非数字字符 |
|
||
|
||
## 最佳实践
|
||
|
||
```
|
||
1. 统一软删除:所有业务数据使用 is_delete 字段,禁止物理删除
|
||
2. 查询过滤:所有查询加 is_delete=False,避免遗漏
|
||
3. 级联处理:删除父记录时同步处理关联的子记录
|
||
4. 批量删除:支持逗号分隔的多 ID,提高操作效率
|
||
5. 错误提示:返回明确的错误信息,便于排查
|
||
6. 操作日志:删除操作记录操作日志,便于审计
|
||
```
|
||
|
||
## 总结
|
||
|
||
删除规范统一使用软删除(`is_delete` 标记),支持逗号分隔的多 ID 批量删除。Service 层解析 ID 列表后通过 ORM 批量更新 `is_delete=True`。所有查询方法自动过滤 `is_delete=False`,业务层无需关心软删除逻辑。级联删除时需同步处理关联数据,保证数据一致性。
|