186 lines
4.6 KiB
Markdown
186 lines
4.6 KiB
Markdown
# 12.4 数据删除规范
|
||
|
||
:::tip 概述
|
||
本页规定业务数据的软删除约定与物理删除禁区,开发涉及删除功能时按本规范实现以保证数据可追溯。
|
||
:::
|
||
|
||
## 软删除约定
|
||
|
||
:::tip 核心原则
|
||
所有业务数据统一使用软删除机制,通过 `is_delete` 字段标记记录状态,不物理删除数据(`SoftDeletesByFlag` Trait + `SoftDeleteFlagScope` 全局范围,不使用 `deleted_at`)。
|
||
:::
|
||
|
||
| is_delete 值 | 含义 |
|
||
|--------------|------|
|
||
| `0` | 正常记录(默认值) |
|
||
| `1` | 已删除记录 |
|
||
|
||
所有查询方法自动过滤 `is_delete = 0`,业务层无需关心已删除数据。
|
||
|
||
## 单条删除
|
||
|
||
### URL 格式
|
||
|
||
```text
|
||
DELETE /api/{module}/delete/{id}
|
||
```
|
||
|
||
### Controller 示例
|
||
|
||
```php
|
||
#[OperationLog('案例演示-删除', OperationLog::TYPE_DELETE, '删除案例ID:{id}')]
|
||
#[Permission('sys:example:delete', '删除案例')]
|
||
public function delete(int $id): JsonResponse
|
||
{
|
||
return $this->_delete($this->exampleService, $id);
|
||
}
|
||
```
|
||
|
||
### 内部流程
|
||
|
||
```text
|
||
Controller::delete($id)
|
||
│
|
||
▼
|
||
Controller 基类::_delete($service, $id)
|
||
│
|
||
▼
|
||
Service::delete($id) → HasCrudOperations::doDelete($id)
|
||
│ (DB::transaction 包裹)
|
||
├─ beforeDelete($id) 钩子(抛异常可拦截,事务回滚)
|
||
│
|
||
├─ $model->find($id)
|
||
│ └─ 不存在 → return false
|
||
│
|
||
├─ $model->softDelete() → UPDATE SET is_delete = 1
|
||
│
|
||
└─ afterDelete($id) 钩子
|
||
```
|
||
|
||
## 批量删除
|
||
|
||
### 请求格式
|
||
|
||
```text
|
||
DELETE /api/{module}/batchDelete
|
||
Content-Type: application/json
|
||
|
||
{ "ids": [1, 2, 3] }
|
||
```
|
||
|
||
### Controller 示例
|
||
|
||
```php
|
||
#[OperationLog('案例演示-批量删除', OperationLog::TYPE_DELETE)]
|
||
#[Permission('sys:example:batchDelete', '批量删除案例')]
|
||
public function batchDelete(): JsonResponse
|
||
{
|
||
$ids = $this->getJsonBody()['ids'] ?? [];
|
||
return $this->_batchDelete($this->exampleService, $ids);
|
||
}
|
||
```
|
||
|
||
### 内部流程
|
||
|
||
```text
|
||
Controller::batchDelete()
|
||
│
|
||
▼
|
||
Controller 基类::_batchDelete($service, $ids)
|
||
│
|
||
├─ ids 为空 → 返回失败 "请选择要删除的数据"
|
||
│
|
||
▼
|
||
Service::batchDelete($ids) → HasCrudOperations::doBatchDelete($ids)
|
||
│ (DB::transaction 包裹)
|
||
├─ beforeBatchDelete($ids) 钩子(整体校验)
|
||
│
|
||
├─ 逐条 beforeDelete($id) 校验
|
||
│
|
||
├─ Model::batchSoftDelete($ids)
|
||
│ └─ UPDATE SET is_delete = 1 WHERE id IN (...)
|
||
│
|
||
└─ afterBatchDelete($ids) 钩子
|
||
```
|
||
|
||
:::info 权限码区别
|
||
单条删除权限为 `sys:{模块}:delete`,批量删除权限为 `sys:{模块}:batchDelete`(代码生成器创建菜单时分别生成),前端按钮需分别控制显隐。
|
||
:::
|
||
|
||
## 删除前校验(beforeDelete 钩子)
|
||
|
||
在 Service 中重写 `beforeDelete` 方法,抛异常可拦截删除(事务自动回滚):
|
||
|
||
```php
|
||
class DeptService extends BaseTenantService
|
||
{
|
||
protected function beforeDelete(int $id): void
|
||
{
|
||
// 检查是否有子级
|
||
$children = Dept::where('parent_id', $id)->where('is_delete', 0)->count();
|
||
if ($children > 0) {
|
||
throw new \Exception('存在子部门,不可删除');
|
||
}
|
||
|
||
// 检查是否被引用
|
||
$userCount = User::where('dept_id', $id)->where('is_delete', 0)->count();
|
||
if ($userCount > 0) {
|
||
throw new \Exception('该部门下有用户,不可删除');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
可用钩子(均在 `HasCrudOperations` 中声明):`beforeDelete($id)`、`afterDelete($id)`、`beforeBatchDelete($ids)`、`afterBatchDelete($ids)`。
|
||
|
||
## 何时可物理删除
|
||
|
||
| 场景 | 方式 | 说明 |
|
||
|------|------|------|
|
||
| 日志数据清理 | 定时任务 | 通过「定时任务」模块(job 表)配置清理 N 天前的日志 |
|
||
| 临时数据 | 定时任务 | 清理过期的临时文件 |
|
||
| 其它 | 审批 | 需经过管理员审批 |
|
||
|
||
## 前端删除规范
|
||
|
||
### 单条删除
|
||
|
||
```vue
|
||
<template>
|
||
<n-button type="error" size="small" @click="handleDelete(row)">删除</n-button>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useMessage, useDialog } from 'naive-ui';
|
||
|
||
const message = useMessage();
|
||
const dialog = useDialog();
|
||
|
||
const handleDelete = async (row) => {
|
||
dialog.warning({
|
||
title: '提示',
|
||
content: '确认删除该记录?',
|
||
positiveText: '确定',
|
||
negativeText: '取消',
|
||
onPositiveClick: async () => {
|
||
await exampleDelete(row.id);
|
||
message.success('删除成功');
|
||
reloadTable();
|
||
},
|
||
});
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 批量删除
|
||
|
||
```vue
|
||
<n-button
|
||
type="error"
|
||
:disabled="!selectedIds.length"
|
||
@click="handleBatchDelete"
|
||
>
|
||
批量删除 ({{ selectedIds.length }})
|
||
</n-button>
|
||
```
|