本文目录导读:

在PHP项目中解决页面重复加载问题,通常需要从前端控制和后端防重两个层面入手,以下是最实用、最常见的解决方案:
前端控制方案
按钮禁用(最简单)
// 提交后立即禁用按钮
document.getElementById('submitBtn').onclick = function() {
this.disabled = true;
this.value = '提交中...';
// 或者 this.innerHTML = '提交中...';
};
防抖与节流
// 防抖:停止操作后一段时间才执行
function debounce(fn, delay = 500) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:规定时间内只执行一次
function throttle(fn, delay = 500) {
let canRun = true;
return function(...args) {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, args);
canRun = true;
}, delay);
};
}
页面跳转或刷新
// 提交成功后跳转到其他页面
window.location.href = 'success.php';
// 或者使用 replace 防止回退
window.location.replace('success.php');
后端防重方案(核心)
表单 Token 机制(最推荐)
// 生成 Token
session_start();
$token = md5(uniqid(mt_rand(), true));
$_SESSION['form_token'] = $token;
// 在表单中隐藏
echo '<input type="hidden" name="form_token" value="' . $token . '">';
// 验证 Token
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['form_token']) || $_POST['form_token'] !== $_SESSION['form_token']) {
die('重复提交或非法请求');
}
// 使用后立即清除
unset($_SESSION['form_token']);
// 处理业务逻辑...
}
基于时间的防重
session_start();
// 记录最后一次提交时间
$last_submit = $_SESSION['last_submit_time'] ?? 0;
$current_time = time();
if ($current_time - $last_submit < 3) { // 3秒内不允许重复提交
die('请勿频繁提交');
}
$_SESSION['last_submit_time'] = $current_time;
数据库唯一约束
// 表结构
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_sn VARCHAR(50) UNIQUE, -- 唯一约束
user_id INT,
created_at TIMESTAMP
);
// 插入时捕获唯一约束异常
try {
$order_sn = 'ORD' . date('YmdHis') . mt_rand(1000, 9999);
$sql = "INSERT INTO orders (order_sn, user_id) VALUES ('$order_sn', $user_id)";
$db->query($sql);
} catch (Exception $e) {
if (strpos($e->getMessage(), 'Duplicate') !== false) {
// 重复提交处理
}
}
使用 Redis 锁(高并发场景)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lock_key = 'order_lock_' . $user_id;
// 尝试获取锁(5秒过期)
if ($redis->setnx($lock_key, time())) {
$redis->expire($lock_key, 5);
// 处理业务...
// 释放锁
$redis->del($lock_key);
} else {
die('请求正在处理中,请稍后');
}
PRG 模式(Post-Redirect-Get)
// 处理完 POST 请求后,重定向
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 处理业务...
// 重定向到成功页面
header('Location: success.php');
exit;
}
完整示例:防重复提交最佳实践
<?php
session_start();
// 表单页面
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
// 生成并存储 Token
$token = bin2hex(random_bytes(16));
$_SESSION['form_token'] = $token;
?>
<form method="POST" onsubmit="return checkSubmit()">
<input type="hidden" name="form_token" value="<?php echo $token; ?>">
<input type="submit" id="submitBtn" value="提交">
</form>
<script>
let submitting = false;
function checkSubmit() {
if (submitting) {
alert('请勿重复提交');
return false;
}
submitting = true;
document.getElementById('submitBtn').disabled = true;
return true;
}
</script>
<?php
exit;
}
// 处理 POST 请求
$user_token = $_POST['form_token'] ?? '';
$session_token = $_SESSION['form_token'] ?? '';
// 1. 验证 Token
if (empty($user_token) || $user_token !== $session_token) {
die('非法请求');
}
// 2. 立即清除 Token(防止重复提交)
unset($_SESSION['form_token']);
// 3. 业务处理
try {
// 数据库操作等...
$result = processOrder();
// 4. PRG 重定向
header('Location: success.php?order_id=' . $result['id']);
exit;
} catch (Exception $e) {
// 错误处理
$_SESSION['error'] = $e->getMessage();
header('Location: error.php');
exit;
}
常见问题场景及对策
| 场景 | 推荐方案 |
|---|---|
| 普通表单提交 | Token + 前端按钮禁用 |
| 支付/订单 | Token + Redis锁 + 数据库唯一约束 |
| 用户注册 | Token + 邮件/短信验证码 |
| 评论/留言 | Token + 时间间隔限制 |
| 文件上传 | Token + 文件唯一性校验 |
| 高并发秒杀 | Redis锁 + 消息队列 |
注意事项
- 不要只依赖前端控制:前端只能提高用户体验,无法真正防重复
- Token 使用一次即失效:确保每次刷新页面生成新 Token
- 考虑 Session 有效期:长时间页面停留可能导致 Token 过期
- 日志记录:对重复提交行为进行记录,方便排查问题
- 用户体验:提交后给出明确的加载反馈,避免用户焦虑重复点击
最推荐组合:Token 机制 + PRG 模式 + 前端按钮禁用,这三个组合基本能覆盖 99% 的重复提交问题。