Skip to content

防重复提交

防重复提交是保障数据一致性的重要措施。通过前端防抖和后端唯一性校验双重保护,防止用户重复点击导致的数据重复提交。后端唯一性校验通过 Pydantic @field_validator 查询数据库实现。

前端防抖

useLockFn

前端提供 useLockFn 钩子函数,实现请求级别的防重复提交:

javascript
import { useLockFn } from '@/utils/lock'

// 包装提交函数,防止重复点击
const handleSubmit = useLockFn(async () => {
    await axios.post('/api/v1/level/add', formData)
    ElMessage.success('添加成功')
})

useLockFn 原理:

javascript
function useLockFn(fn) {
    let isLocked = false
    return async (...args) => {
        if (isLocked) return
        isLocked = true
        try {
            return await fn(...args)
        } finally {
            isLocked = false
        }
    }
}

按钮 loading

ElementPlus 的按钮组件支持 loading 属性,请求期间禁用按钮:

vue
<el-button
    type="primary"
    :loading="submitting"
    @click="handleSubmit"
>
    提交
</el-button>

<script setup>
const submitting = ref(false)

async function handleSubmit() {
    submitting.value = true
    try {
        await axios.post('/api/v1/level/add', formData)
        ElMessage.success('添加成功')
    } finally {
        submitting.value = false
    }
}
</script>

后端幂等性

唯一性约束

通过数据库唯一索引和 Pydantic 验证器保证数据唯一性:

python
class LevelForm(BaseForm):
    name: str = Field(..., min_length=1, max_length=150)

    @field_validator('name')
    @classmethod
    def check_unique_name(cls, v, info):
        # 查询数据库校验唯一性
        if level_repo.exists_by_field('name', v, exclude_id=info.data.get('id')):
            raise ValueError('职级名称已存在')
        return v

软删除 + 唯一索引

软删除场景下,唯一索引需要特殊处理:

sql
-- MySQL: 唯一索引包含 is_delete 字段
ALTER TABLE fastapi_level ADD UNIQUE INDEX uk_name (name, is_delete);

-- PostgreSQL: 部分唯一索引
CREATE UNIQUE INDEX uk_name ON fastapi_level (name) WHERE is_delete = 0;

最佳实践

1. 前端防抖:useLockFn 或按钮 loading,防止用户重复点击
2. 后端校验:Pydantic 验证器 + 数据库唯一索引,双重保障
3. 幂等设计:相同请求多次执行结果一致
4. 错误提示:重复提交时返回明确的错误提示

总结

防重复提交方案具备以下特点:

1. 前端防抖:useLockFn 函数级防抖,按钮 loading 状态控制
2. 后端校验:Pydantic 验证器查询数据库校验唯一性
3. 数据库约束:唯一索引兜底,防止并发重复插入
4. 双重保护:前端防抖 + 后端校验,确保数据一致性
5. 用户体验:重复点击时按钮禁用,返回明确提示

小蚂蚁云团队 · 提供技术支持