如何防止用户在数秒内多次执行PHP脚本?
PHP脚本实现指定间隔防重复执行方案
你的原代码无效的核心原因是PHP的无状态特性——每次HTTP请求都会启动独立的PHP进程,脚本内的$timediff变量在请求结束后就会销毁,下次请求根本无法读取到之前的值,导致检查逻辑完全失效。要实现跨请求的执行间隔控制,必须把上次执行的时间存储到持久化介质中(比如文件、数据库、缓存)。
方案一:文件存储(简单易实现)
通过文件记录上次执行的时间戳,每次执行前读取并检查时间差,执行完成后更新时间戳。为了避免并发请求绕过检查,建议给文件加排他锁。
示例代码:
// 配置:执行间隔(秒)、存储时间戳的文件路径 $interval = 10; $timeFile = __DIR__ . '/last_run.lock'; $currentTime = microtime(true); $lastRunTime = 0; // 打开文件并加排他锁,防止并发 $handle = fopen($timeFile, 'c+'); if ($handle && flock($handle, LOCK_EX)) { // 读取上次执行时间 $lastRunTime = (float)fread($handle, filesize($timeFile)) ?: 0; // 检查时间间隔 if ($currentTime - $lastRunTime < $interval) { echo "请等待{$interval}秒后再尝试"; flock($handle, LOCK_UN); fclose($handle); exit; } // 执行你的业务逻辑 echo "脚本正在执行..."; // --- 这里写你的业务代码 --- // 更新上次执行时间 ftruncate($handle, 0); rewind($handle); fwrite($handle, $currentTime); // 释放锁并关闭文件 flock($handle, LOCK_UN); fclose($handle); } else { echo "无法获取执行锁,请稍后再试"; exit; }
方案二:数据库存储(适合多环境/分布式场景)
如果你的服务有数据库,可以用表记录脚本的最后执行时间,适合多服务器或需要更复杂控制的场景。
- 先创建存储表(以MySQL为例):
CREATE TABLE script_exec_log ( id INT AUTO_INCREMENT PRIMARY KEY, script_identifier VARCHAR(100) NOT NULL COMMENT '脚本唯一标识', last_exec_time DECIMAL(16,6) NOT NULL COMMENT '上次执行时间戳(微秒级)', UNIQUE KEY idx_script_id (script_identifier) );
- PHP示例代码:
$interval = 10; $scriptId = 'your_unique_script_name'; $currentTime = microtime(true); // 连接数据库(替换为你的数据库配置) $pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8', 'username', 'password'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { // 读取上次执行时间 $stmt = $pdo->prepare("SELECT last_exec_time FROM script_exec_log WHERE script_identifier = ?"); $stmt->execute([$scriptId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); $lastRunTime = $row ? (float)$row['last_exec_time'] : 0; if ($currentTime - $lastRunTime < $interval) { echo "请等待{$interval}秒后再尝试"; exit; } // 执行业务逻辑 echo "脚本正在执行..."; // --- 业务代码 --- // 更新执行时间(不存在则插入) $stmt = $pdo->prepare("INSERT INTO script_exec_log (script_identifier, last_exec_time) VALUES (?, ?) ON DUPLICATE KEY UPDATE last_exec_time = ?"); $stmt->execute([$scriptId, $currentTime, $currentTime]); } catch (PDOException $e) { echo "数据库错误:" . $e->getMessage(); exit; }
注意事项
- 原代码中
microseconds(true)是拼写错误,正确的函数是microtime(true),返回微秒级的时间戳。 - 如果是针对单个用户的限制(比如用户点击按钮防重复提交),需要结合用户标识(如SESSION、用户ID)来存储时间戳,避免不同用户互相影响。
- 分布式场景下,建议用Redis等缓存存储时间戳,性能更高且支持原子操作。
内容的提问来源于stack exchange,提问作者ThN
相关产品推荐
相关产品推荐

