166 lines
4.4 KiB
Markdown
166 lines
4.4 KiB
Markdown
# 删除规范
|
||
|
||
## 概述
|
||
|
||
统一使用软删除机制,通过 `is_delete` 字段标记记录状态,不物理删除数据。删除操作分为单条删除和批量删除两种方式。
|
||
|
||
:::tip 软删除约定
|
||
- `is_delete = 0`:正常记录(默认值)
|
||
- `is_delete = 1`:已删除记录
|
||
- 所有查询方法自动过滤 `is_delete = 0`,业务层无需关心
|
||
:::
|
||
|
||
## 单条删除
|
||
|
||
### URL 格式
|
||
|
||
```
|
||
DELETE /api/v1/{module}/delete/{id}
|
||
```
|
||
|
||
支持逗号分隔的多 ID 删除:
|
||
|
||
```
|
||
DELETE /api/v1/position/delete/1,2,3
|
||
```
|
||
|
||
### Endpoint 示例
|
||
|
||
```python
|
||
@router.delete('/delete/{id}', summary='删除岗位')
|
||
@permission_required("sys:position:delete")
|
||
@check_demo
|
||
def delete(request: Request, id):
|
||
"""根据ID删除单个岗位"""
|
||
return position_service.delete(id)
|
||
```
|
||
|
||
:::tip 装饰器顺序
|
||
删除接口的装饰器顺序:`@router.delete` → `@permission_required` → `@check_demo`
|
||
:::
|
||
|
||
## 批量删除
|
||
|
||
### 请求格式
|
||
|
||
```
|
||
DELETE /api/v1/{module}/batchDelete
|
||
Content-Type: application/json
|
||
|
||
[1, 2, 3]
|
||
```
|
||
|
||
### Endpoint 示例
|
||
|
||
```python
|
||
@router.delete('/batchDelete', summary='批量删除岗位')
|
||
@permission_required("sys:position:batchDelete")
|
||
@check_demo
|
||
async def batch_delete(request: Request):
|
||
"""批量删除岗位"""
|
||
return await position_service.batch_delete(request)
|
||
```
|
||
|
||
:::warning async def 规则
|
||
批量删除端点必须为 `async def`,因为 `parse_batch_ids(request)` 需要异步读取请求体。
|
||
:::
|
||
|
||
## batch_delete_with_r 模式
|
||
|
||
### 原理
|
||
|
||
Repository 层的 `batch_delete` 只做数据原语(软删除 + 返回条数),Service 层通过 `BaseService.batch_delete_with_r()` 静态方法统一封装 R 响应。
|
||
|
||
```python
|
||
@staticmethod
|
||
def batch_delete_with_r(repo: BaseRepository, ids) -> R:
|
||
"""按逗号分隔ID软删除并返回标准R响应"""
|
||
id_list = parse_id_list(str(ids))
|
||
if not id_list:
|
||
return R.failed("记录ID不存在")
|
||
|
||
count = repo.batch_delete(ids)
|
||
if count != len(id_list):
|
||
return R.failed("记录不存在")
|
||
|
||
return R.ok(msg="本次共删除{0}条数据".format(count))
|
||
```
|
||
|
||
### 自定义 Service 复用
|
||
|
||
```python
|
||
class PositionService(BaseService[Position]):
|
||
def _before_delete(self, ids) -> Optional[str]:
|
||
"""删除前校验:存在用户引用时禁止删除"""
|
||
id_list = parse_id_list(str(ids))
|
||
if id_list and user_repo.filter(
|
||
User.position_id.in_(id_list), User.is_delete == 0
|
||
).first():
|
||
return "存在用户引用该岗位,请先调整用户岗位"
|
||
return None
|
||
```
|
||
|
||
:::tip _before_delete 钩子
|
||
- 返回 `None`:放行,继续删除
|
||
- 返回字符串:拦截,返回 `R.failed(字符串)`
|
||
- 用于检查子级引用、业务约束等
|
||
:::
|
||
|
||
## 删除流程
|
||
|
||
```
|
||
1. Endpoint 接收请求
|
||
↓
|
||
2. Service.delete(ids) 或 Service.batch_delete(request)
|
||
↓
|
||
3. _before_delete(ids) 钩子校验
|
||
↓(拦截则返回 R.failed)
|
||
4. batch_delete_with_r(repo, ids)
|
||
↓
|
||
5. repo.batch_delete(ids) 软删除
|
||
↓
|
||
6. 返回 R.ok(msg="本次共删除N条数据")
|
||
```
|
||
|
||
## Repository 层实现
|
||
|
||
```python
|
||
def batch_delete(self, ids_str) -> int:
|
||
"""按ID软删除,返回实际删除条数"""
|
||
id_list = parse_id_list(str(ids_str))
|
||
if not id_list:
|
||
return 0
|
||
|
||
records = self.db.query(self.model).filter(
|
||
self.model.id.in_(id_list), self._soft_col() == 0
|
||
).all()
|
||
|
||
for record in records:
|
||
setattr(record, self.soft_delete_col, 1)
|
||
|
||
return len(records)
|
||
```
|
||
|
||
:::tip 返回值说明
|
||
`batch_delete` 返回实际软删除的条数。如果传入的 ID 中包含已删除或不存在的记录,返回值会小于入参数量,此时 `batch_delete_with_r` 会返回失败提示。
|
||
:::
|
||
|
||
## 权限标识
|
||
|
||
| 操作 | 权限标识 | 说明 |
|
||
|------|---------|------|
|
||
| 单条删除 | `sys:{module}:delete` | 删除单条记录 |
|
||
| 批量删除 | `sys:{module}:batchDelete` | 批量删除记录 |
|
||
|
||
## 常见错误提示
|
||
|
||
| 提示 | 原因 |
|
||
|------|------|
|
||
| "记录ID不存在" | 入参为空或格式错误 |
|
||
| "记录不存在" | 部分或全部 ID 对应的记录已删除/不存在 |
|
||
| "存在用户引用该岗位" | _before_delete 钩子拦截 |
|
||
|
||
## 总结
|
||
|
||
删除规范统一使用软删除(`is_delete` 标记),支持单条删除(逗号分隔 ID)和批量删除(JSON 数组)。Repository 层做数据原语,Service 层通过 `batch_delete_with_r` 统一封装响应。自定义 Service 通过 `_before_delete` 钩子添加业务校验。批量删除端点必须为 `async def`。
|