import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.B-QBKwc1.js";const g=JSON.parse('{"title":"数据删除规范","description":"本页规定业务数据的软删除约定与物理删除禁区,开发涉及删除功能时按本规范实现以保证数据可追溯。","frontmatter":{},"headers":[],"relativePath":"zh/standards/deletion.md","filePath":"zh/standards/deletion.md"}'),h={name:"zh/standards/deletion.md"};function l(k,s,e,p,d,r){return a(),n("div",null,[...s[0]||(s[0]=[t(`

12.4 数据删除规范 ​

概述

本页规定业务数据的软删除约定与物理删除禁区,开发涉及删除功能时按本规范实现以保证数据可追溯。

软删除约定 ​

核心原则

所有业务数据统一使用软删除机制,通过 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) 钩子

权限码区别

单条删除权限为 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>
`,32)])])}const y=i(h,[["render",l]]);export{g as __pageData,y as default};