Become a sponsor

防重复提交
防重复提交是保障数据一致性的重要措施。通过前端防抖和后端唯一性校验双重保护,防止用户重复点击导致的数据重复提交。后端唯一性校验通过 WTForms 自定义校验器查询数据库实现。
前端提供 useLockFn 钩子函数,实现请求级别的防重复提交:
import { useLockFn } from '@/utils/useLockFn'
// 包装提交函数,防止重复点击
const { lockFn: handleSubmit } = useLockFn(async () => {
await axios.post('/api/v1/level/add', formData)
ElMessage.success('添加成功')
})useLockFn 原理:
import { ref } from 'vue'
export function useLockFn(fn) {
const isLock = ref(false)
const lockFn = async (...args) => {
if (isLock.value) return
isLock.value = true
try {
await fn(...args)
} finally {
isLock.value = false
}
}
return { isLock, lockFn }
}ElementPlus 的按钮组件支持 loading 属性,请求期间禁用按钮:
<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>通过数据库唯一索引和 WTForms 验证器保证数据唯一性:
from wtforms import StringField
from wtforms.validators import DataRequired, Length, ValidationError
class LevelForm(BaseForm):
name = StringField('职级名称', validators=[
DataRequired(message='职级名称不能为空'),
Length(min=1, max=150, message='职级名称长度必须在1-150个字符之间')
])
def validate_name(self, field):
# 查询数据库校验唯一性
exclude_id = self.id.data if self.id.data else None
if LevelRepository.exists_by([Level.name == field.data], exclude_id):
raise ValidationError('职级名称已存在')软删除场景下,唯一索引需要特殊处理:
-- MySQL: 唯一索引包含 is_delete 字段
ALTER TABLE flask_level ADD UNIQUE INDEX uk_name (name, is_delete);
-- PostgreSQL: 部分唯一索引
CREATE UNIQUE INDEX uk_name ON flask_level (name) WHERE is_delete = 0;1. 前端防抖:useLockFn 或按钮 loading,防止用户重复点击
2. 后端校验:WTForms 验证器 + 数据库唯一索引,双重保障
3. 幂等设计:相同请求多次执行结果一致
4. 错误提示:重复提交时返回明确的错误提示防重复提交方案具备以下特点:
1. 前端防抖:useLockFn 函数级防抖,按钮 loading 状态控制
2. 后端校验:WTForms 验证器查询数据库校验唯一性
3. 数据库约束:唯一索引兜底,防止并发重复插入
4. 双重保护:前端防抖 + 后端校验,确保数据一致性
5. 用户体验:重复点击时按钮禁用,返回明确提示