PHP会话与AJAX问题:后台计时器无法稳定更新后端数据
浏览器后台运行时,PHP计时器耗时数据无法可靠写入MySQL的解决方案
问题根源分析
- 浏览器后台节流:移动端浏览器会限制后台页面的JS执行频率,定时器间隔被拉长甚至暂停,导致AJAX请求无法按时发送,数据更新中断。
- 会话有效性问题:免费主机(如InfinityFree)可能存在会话回收机制,加上后台状态下Cookie更新不及时,容易导致PHP会话过期,后端因
$_SESSION['uid']为空无法执行SQL更新。 - 前端依赖风险:直接使用DOM元素的
innerText作为耗时数据,一旦后台节流导致计时偏差,数据准确性无法保证;且无请求重试机制,失败后直接丢失数据。 - SQL逻辑缺陷:
WHERE条件依赖服务器当前日期,若用户跨天使用计时器,会找不到对应记录;直接拼接$_SESSION['uid']存在SQL注入风险,且会话失效时会引发语法错误。
针对性解决方案
1. 改用后端计算耗时,避免前端依赖
放弃前端计算的HH:MM:SS数据,改为前端传递开始时间戳和当前时间戳,由后端计算准确耗时,彻底避免后台节流导致的计时偏差。
2. 使用更可靠的请求方式
替换fetch()为navigator.sendBeacon(),该API专为后台数据上报设计,浏览器会保证请求完成,即使页面处于后台或即将关闭。同时添加本地缓存机制,请求失败时暂存数据,页面恢复后补发。
3. 会话可靠性优化
- 后端增加会话有效性检查,返回明确的过期提示,前端可据此引导用户重新验证。
- 可考虑用JWT令牌替代PHP会话,避免Cookie在后台失效的问题。
4. 后端SQL与安全优化
- 使用预处理语句防止SQL注入,同时避免直接拼接
$_SESSION['uid']导致的语法错误。 - 修改
WHERE条件为使用开始时间的日期,而非服务器当前日期,解决跨天更新问题。
修改后的代码示例
前端代码(含缓存与补发逻辑)
function updateElapsedTime() { let startTime = getCookie('startTime'); let currentTime = new Date().toISOString(); // 仅传递时间戳,由后端计算耗时 const payload = { startTime: startTime, currentTime: currentTime }; // 优先使用sendBeacon保证后台发送可靠性 if (navigator.sendBeacon) { const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' }); const success = navigator.sendBeacon('updateElapsedTime.php', blob); if (!success) saveToLocalCache(payload); } else { fetch('updateElapsedTime.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), keepalive: true }) .then(response => { if (!response.ok) throw new Error('Request failed'); return response.json(); }) .catch(() => saveToLocalCache(payload)); } } // 缓存待发送的数据到localStorage function saveToLocalCache(data) { let cache = JSON.parse(localStorage.getItem('timerUpdates') || '[]'); cache.push(data); localStorage.setItem('timerUpdates', JSON.stringify(cache)); } // 页面恢复焦点时补发缓存数据 window.addEventListener('focus', () => { let cache = JSON.parse(localStorage.getItem('timerUpdates') || '[]'); if (cache.length === 0) return; cache.forEach(data => { fetch('updateElapsedTime.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(() => { // 移除已成功发送的缓存 cache = cache.filter(item => item !== data); localStorage.setItem('timerUpdates', JSON.stringify(cache)); }); }); });
后端PHP代码(含会话检查与预处理语句)
<?php include("connection.php"); session_start(); date_default_timezone_set("Asia/Kolkata"); // 检查会话有效性 if (!isset($_SESSION['uid'])) { http_response_code(401); echo json_encode(["success" => false, "message" => "Session expired"]); exit; } if ($_SERVER["REQUEST_METHOD"] == "POST") { $data = json_decode(file_get_contents('php://input'), true); // 验证参数完整性 if (!$data || !isset($data['startTime'], $data['currentTime'])) { http_response_code(400); echo json_encode(["success" => false, "message" => "Missing parameters"]); exit; } // 计算准确耗时 $startTime = new DateTime($data['startTime']); $currentTime = new DateTime($data['currentTime']); $interval = $startTime->diff($currentTime); $elapsedTime = $interval->format('%H:%I:%S'); $logoutTime = $currentTime->format('H:i:s'); $startDate = $startTime->format('Y-m-d'); // 使用开始时间的日期 $employeeId = $_SESSION['uid']; // 预处理SQL语句,防止注入 $updateSql = "UPDATE stopwatch_data SET elapsedTime = ?, logoutTime = ? WHERE start_date = ? AND employee_id = ?"; $stmt = $con->prepare($updateSql); $stmt->bind_param("sssi", $elapsedTime, $logoutTime, $startDate, $employeeId); if ($stmt->execute()) { echo json_encode(["success" => true]); } else { echo json_encode(["success" => false, "message" => "Update failed: " . $stmt->error]); } $stmt->close(); } else { http_response_code(400); echo json_encode(["success" => false, "message" => "Invalid request method"]); } $con->close(); ?>
额外优化建议
- 调整前端定时器间隔:从每秒一次改为每30秒或1分钟一次,减少请求数量,降低被浏览器节流和主机限制的概率。
- 增加后端日志:记录每次更新请求的参数、会话状态和执行结果,方便排查问题。
- InfinityFree主机限制:避免短时间内发送大量请求,可合并多次更新为单次请求(如缓存多个时间点,批量发送)。
内容的提问来源于stack exchange,提问作者Shaikh Naiem
相关产品推荐
相关产品推荐

