如何防止PHP(CodeIgniter)脚本重复执行?
解决CodeIgniter脚本因浏览器/网络问题重复执行导致重复记录的方案
我之前处理过不少类似的重复提交问题,尤其是网络不稳定或者用户误操作(加上浏览器重试机制)引发的重复记录,给你几个在CodeIgniter框架下实用的解决方案,建议多层结合来彻底解决:
1. 前端+后端的一次性令牌防护(核心方案)
CodeIgniter自带CSRF防护,但默认的CSRF令牌是会话周期内有效,没法防同一会话内的重复提交。我们可以扩展成一次性令牌:
- 后端生成唯一令牌:在渲染表单的控制器方法里,生成一个随机字符串(比如用
uniqid()或者UUID),存入Session,同时传给前端表单的隐藏字段 - 提交验证:接收请求时,先比对前端传来的令牌和Session里的令牌,匹配成功后立刻销毁Session里的令牌,再执行业务逻辑;如果不匹配或者令牌不存在,直接拒绝请求
示例代码:
// 渲染表单的控制器方法 public function create() { $token = uniqid('submit_', true); $this->session->set_userdata('submit_token', $token); $data['submit_token'] = $token; $this->load->view('your_form_view', $data); } // 处理提交的控制器方法 public function store() { $postedToken = $this->input->post('submit_token'); $sessionToken = $this->session->userdata('submit_token'); if (!$postedToken || $postedToken !== $sessionToken) { $this->session->set_flashdata('error', '请勿重复提交'); redirect('your_form_url'); return; } // 销毁令牌,防止重复使用 $this->session->unset_userdata('submit_token'); // 执行你的插入/业务逻辑 // ... }
2. 数据库层面添加唯一约束(最后一道防线)
即使前面的防护失效,数据库的唯一约束能直接阻止重复数据插入。根据你的业务场景,找到能唯一标识一条记录的字段组合(比如用户ID+操作类型+日期,或者订单号、业务流水号等),给这个组合添加唯一索引:
ALTER TABLE `your_table` ADD UNIQUE KEY `unique_record` (`user_id`, `action_type`, DATE(`created_at`));
这样当重复请求尝试插入相同组合的记录时,数据库会抛出Duplicate Entry错误,你可以在CodeIgniter里捕获这个错误,返回友好提示。
3. 前端禁用提交按钮(防手动重复点击)
虽然没法完全防浏览器的网络重试,但能有效阻止用户因为卡顿多次点击按钮。在表单提交事件里,立刻禁用按钮:
document.getElementById('submit-btn').addEventListener('click', function(e) { this.disabled = true; // 如果是表单提交,不要阻止默认行为,只是禁用按钮 document.getElementById('your-form').submit(); });
4. 缓存实现请求去重(针对网络重试)
针对浏览器因为网络超时自动重试的情况,可以用缓存(比如CodeIgniter自带的缓存,或者Redis)记录最近的请求标识,短时间内拒绝重复请求:
- 前端生成一个唯一请求ID(比如UUID),随表单一起提交
- 后端先检查缓存里是否存在这个请求ID,如果存在说明是重复请求,直接拒绝;如果不存在,就把请求ID存入缓存,设置过期时间(比如10分钟,覆盖你的重复记录时间差1-2分钟)
示例代码:
$requestId = $this->input->post('request_id'); // 用CodeIgniter的文件缓存,也可以换成Redis缓存 if ($this->cache->get('request_' . $requestId)) { echo json_encode(['status' => 'error', 'message' => '请求已处理,请勿重复提交']); return; } // 存入缓存,10分钟后过期 $this->cache->save('request_' . $requestId, true, 600); // 执行业务逻辑 // ...
5. 插入前检查最近重复记录(补充方案)
在执行插入操作前,先查询数据库,看看最近N分钟内是否存在相同的业务记录,如果有就跳过插入:
$userId = $this->input->post('user_id'); $businessKey = $this->input->post('business_key'); // 业务上唯一的字段,比如订单号、操作内容等 // 检查最近2分钟内的重复记录 $duplicate = $this->db->where('user_id', $userId) ->where('business_key', $businessKey) ->where('created_at >=', date('Y-m-d H:i:s', strtotime('-2 minutes'))) ->get('your_table') ->row(); if ($duplicate) { $this->session->set_flashdata('error', '该操作已提交,请勿重复执行'); redirect('your_form_url'); return; } // 执行插入逻辑 // ...
总结
建议组合使用这些方案:前端禁用按钮 + 后端一次性令牌 + 数据库唯一约束,这样从用户操作、请求验证、数据存储三个层面全方位防护,基本能解决你遇到的重复记录问题。
内容的提问来源于stack exchange,提问作者Akash lal
相关产品推荐
相关产品推荐

