求助:表单数据为何会被多次重复插入数据库?
解决表单重复插入数据库的问题
我来帮你排查并解决这个重复插入的问题,先从代码里的几个关键点分析:
可能的触发原因
1. 表单提交事件被重复绑定
你的JS代码是在$(document).ready()里直接绑定#create_account的submit事件,如果页面存在动态加载(比如你用load方法刷新表单区域),每次加载都会重新绑定一次事件,导致提交时触发多次请求,自然就会重复插入数据。
2. 未限制重复点击提交按钮
如果用户快速多次点击提交按钮,在第一次请求还没完成时就发起新请求,也会导致多条重复数据插入。
3. 后端缺乏重复提交防护
服务器端没有做唯一性校验(比如邮箱是否已存在),也没有防重复提交的机制,只要收到请求就执行插入,哪怕是重复请求。
另外,你的PHP代码还存在严重的SQL注入风险,这个也得顺便修复,不然安全隐患很大。
具体解决方案
方案一:修复前端JS,防止重复触发请求
把事件绑定改成事件委托,并且在提交后禁用按钮,避免重复点击:
$(document).ready(function(){ // 用事件委托绑定,即使表单动态加载也只会绑定一次 $(document).on("submit", "#create_account", function(event){ event.preventDefault(); const $form = $(this); const $submitBtn = $form.find("#create_accountSbm"); // 提交后立刻禁用按钮,防止重复点击 $submitBtn.prop("disabled", true).text("提交中..."); // 直接序列化表单数据,比手动取值更简洁不易出错 const formData = $form.serialize(); $("#return_new_Account").load('/blog/form', formData, function(response, status){ // 请求完成后恢复按钮状态(如果需要允许用户再次提交的话) $submitBtn.prop("disabled", false).text("创建账户"); // 如果请求失败可以在这里提示用户 if(status === "error") { $("#return_new_Account").text("提交失败,请重试"); } }); }); });
方案二:后端添加唯一性校验+修复SQL注入
1. 先检查邮箱是否已存在,避免重复插入
修改你的业务逻辑代码,在插入前先查询邮箱是否已注册:
if(empty($validate->message)) { $dbo = database::getInstance(); // 先校验邮箱是否已存在 $checkSql = "SELECT userEmail FROM " . $user->_table . " WHERE userEmail = ?"; $dbo->doQuery($checkSql, [$_POST['email']]); if($dbo->_numRows === 0) { // 邮箱不存在才执行插入 $data = array( 'userFname' => $_POST['firstname'], 'userLname' => $_POST['lastname'], 'userEmail' => $_POST['email'], 'userPsw' => $_POST['password'], ); $user->insert($data); } else { $validate->message = "该邮箱已经注册过啦"; } }
2. 用预处理语句修复SQL注入问题
你的原代码直接把用户输入拼接到SQL里,非常容易被攻击,改成预处理语句:
// 修改doQuery方法,支持预处理参数 public function doQuery($sql, $params = []){ $stmt = $this->_conn->prepare($sql); if(!empty($params)){ // 根据参数类型生成占位符类型字符串,这里假设都是字符串,实际可以按需调整 $types = str_repeat('s', count($params)); $stmt->bind_param($types, ...$params); } $stmt->execute(); $this->_reuslt = $stmt->get_result(); $this->_numRows = $this->_reuslt->num_rows; } // 修改insert方法,使用预处理语句 function insert($data){ $dbo = database::getInstance(); $columns = implode(", ", array_keys($data)); // 生成和参数数量对应的?占位符 $placeholders = implode(", ", array_fill(0, count($data), '?')); $sql = "INSERT INTO " . $this->_table . " ({$columns}) VALUES ({$placeholders})"; $dbo->doQuery($sql, array_values($data)); }
方案三:添加CSRF令牌,彻底防止重复提交和跨站请求
这个是进阶的防护措施,能有效防止恶意重复提交和跨站请求伪造:
- 在表单里添加隐藏的CSRF令牌:
<form id="create_account" method="post"> <!-- 你的其他表单字段 --> <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>"> <button type="submit" id="create_accountSbm">创建账户</button> </form>
- 在PHP页面开头生成并存储令牌:
session_start(); // 生成唯一的CSRF令牌 if(empty($_SESSION['csrf_token'])){ $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); }
- 在业务逻辑里验证令牌:
session_start(); // 验证CSRF令牌是否有效 if(empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']){ die("无效的请求,请刷新页面重试"); } // 验证后销毁令牌,防止重复使用 unset($_SESSION['csrf_token']); // 后续的表单验证和插入逻辑...
按照上面的步骤调整后,既能解决重复插入的问题,又能修复安全漏洞,应该就能正常工作了。
内容的提问来源于stack exchange,提问作者Oliver Larsen
相关产品推荐
相关产品推荐

