本文目录导读:

在PHP项目中实现防重复提交有多种方法,以下是几种常见且有效的方案:
基于Token的防重复提交(最常用)
前端实现
<form action="submit.php" method="POST">
<input type="hidden" name="token" value="<?php echo generateToken(); ?>">
<!-- 其他表单字段 -->
<button type="submit">提交</button>
</form>
后端实现
<?php
session_start();
// 生成Token
function generateToken() {
$token = bin2hex(random_bytes(32));
$_SESSION['form_token'] = $token;
$_SESSION['token_time'] = time();
return $token;
}
// 验证Token
function validateToken($token) {
if (!isset($_SESSION['form_token']) || !isset($_SESSION['token_time'])) {
return false;
}
// 检查Token是否匹配
if ($_SESSION['form_token'] !== $token) {
return false;
}
// 可选:检查Token过期时间(例如5分钟)
$max_lifetime = 300; // 5分钟
if (time() - $_SESSION['token_time'] > $max_lifetime) {
return false;
}
// 清除Token,防止重复使用
unset($_SESSION['form_token']);
unset($_SESSION['token_time']);
return true;
}
// 接收表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['token'] ?? '';
if (!validateToken($token)) {
die('无效的提交,请刷新页面重试');
}
// 处理业务逻辑
// ...
}
?>
基于时间戳的防重复提交
<?php
session_start();
// 检查提交间隔
function checkSubmissionInterval($min_interval = 5) {
$current_time = time();
if (isset($_SESSION['last_submit_time'])) {
$elapsed = $current_time - $_SESSION['last_submit_time'];
if ($elapsed < $min_interval) {
return false; // 提交过于频繁
}
}
$_SESSION['last_submit_time'] = $current_time;
return true;
}
// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!checkSubmissionInterval(3)) { // 最小间隔3秒
die('请勿频繁提交');
}
// 处理业务逻辑
}
?>
基于数据库的唯一约束
方案A:使用唯一索引
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) UNIQUE, -- 唯一约束
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
<?php
// 生成唯一订单号
function generateOrderNo($user_id) {
return date('YmdHis') . $user_id . mt_rand(1000, 9999);
}
// 插入订单
function createOrder($order_no, $user_id) {
global $pdo;
try {
$stmt = $pdo->prepare("INSERT INTO orders (order_no, user_id) VALUES (?, ?)");
$stmt->execute([$order_no, $user_id]);
return true;
} catch (PDOException $e) {
// 唯一约束冲突,说明重复提交
if ($e->errorInfo[1] == 1062) {
return false; // 重复提交
}
throw $e;
}
}
?>
方案B:使用数据库锁
<?php
// 使用Redis实现分布式锁
function acquireLock($key, $ttl = 10) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 尝试获取锁
$lock = $redis->set($key, 1, ['NX', 'EX' => $ttl]);
return $lock;
}
function releaseLock($key) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->del($key);
}
// 使用示例
$lockKey = 'order:user:' . $user_id;
if (!acquireLock($lockKey)) {
die('正在处理中,请勿重复提交');
}
try {
// 处理业务逻辑
// ...
} finally {
releaseLock($lockKey);
}
?>
前端+后端双重验证
前端JavaScript实现
<script>
let isSubmitting = false;
document.querySelector('form').addEventListener('submit', function(e) {
if (isSubmitting) {
e.preventDefault();
alert('请勿重复提交');
return;
}
isSubmitting = true;
// 可选:禁用提交按钮
document.querySelector('button[type="submit"]').disabled = true;
});
</script>
后端验证
<?php
// 结合多种方法
session_start();
// 1. 生成Token
function generateFormToken() {
$token = bin2hex(random_bytes(32));
$_SESSION['form_token_' . $token] = time();
return $token;
}
// 2. 验证Token
function validateFormToken($token) {
if (!isset($_SESSION['form_token_' . $token])) {
return false;
}
// 检查是否过期(30分钟)
if (time() - $_SESSION['form_token_' . $token] > 1800) {
unset($_SESSION['form_token_' . $token]);
return false;
}
// 删除已使用的Token
unset($_SESSION['form_token_' . $token]);
return true;
}
// 3. 检查提交频率
function checkRateLimit($user_id, $action, $limit = 5, $window = 60) {
$key = "rate_limit:{$user_id}:{$action}";
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$current = $redis->incr($key);
if ($current === 1) {
$redis->expire($key, $window);
}
return $current <= $limit;
}
?>
完整示例:综合方案
<?php
session_start();
class AntiDuplicateSubmit {
private $pdo;
private $redis;
private $lockKey;
public function __construct($pdo, $redis = null) {
$this->pdo = $pdo;
$this->redis = $redis;
}
// 生成唯一标识
public function generateId() {
return bin2hex(random_bytes(16));
}
// 检查是否重复提交
public function checkDuplicate($submitId, $userId) {
// 方法1:检查会话中的提交ID
if (isset($_SESSION['submit_id']) && $_SESSION['submit_id'] === $submitId) {
return true; // 重复提交
}
// 方法2:数据库检查(如果已经存在相同数据)
$stmt = $this->pdo->prepare("SELECT id FROM submissions WHERE submit_id = ?");
$stmt->execute([$submitId]);
if ($stmt->fetch()) {
return true; // 重复提交
}
// 方法3:Redis锁
if ($this->redis) {
$lockKey = "submit:lock:{$userId}";
if ($this->redis->exists($lockKey)) {
return true; // 正在处理中
}
}
return false;
}
// 标记提交
public function markSubmitted($submitId, $userId) {
$_SESSION['submit_id'] = $submitId;
// 插入数据库
$stmt = $this->pdo->prepare("INSERT INTO submissions (submit_id, user_id) VALUES (?, ?)");
$stmt->execute([$submitId, $userId]);
// 设置Redis锁
if ($this->redis) {
$lockKey = "submit:lock:{$userId}";
$this->redis->setex($lockKey, 10, time());
}
}
}
// 使用示例
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$antiDup = new AntiDuplicateSubmit($pdo);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$submitId = $_POST['submit_id'] ?? '';
$userId = $_SESSION['user_id'] ?? 0;
if (empty($submitId)) {
throw new Exception('无效的提交');
}
if ($antiDup->checkDuplicate($submitId, $userId)) {
die('请勿重复提交');
}
// 标记为已提交
$antiDup->markSubmitted($submitId, $userId);
// 处理业务逻辑
// ...
echo '提交成功';
}
} catch (Exception $e) {
echo '错误:' . $e->getMessage();
}
?>
推荐实践建议
- 组合使用多种方法:前端防抖 + 后端Token验证 + 数据库唯一约束
- 设置适当的过期时间:Token、锁等设置合理的TTL
- 用户友好提示:不仅仅是阻止提交,还要给出明确提示
- 考虑分布式环境:使用Redis等分布式锁方案
- 记录日志:记录重复提交的尝试,便于排查问题
- 避免影响正常用户体验:设置合理的时间间隔(一般2-5秒)
选择哪种方法取决于你的具体场景:
- 简单表单:Token方案足够
- 高并发场景:推荐使用Redis分布式锁
- 严格的业务要求:数据库唯一约束是最可靠的
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播