You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP会话与AJAX问题:后台计时器无法稳定更新后端数据

浏览器后台运行时,PHP计时器耗时数据无法可靠写入MySQL的解决方案

问题根源分析

  1. 浏览器后台节流:移动端浏览器会限制后台页面的JS执行频率,定时器间隔被拉长甚至暂停,导致AJAX请求无法按时发送,数据更新中断。
  2. 会话有效性问题:免费主机(如InfinityFree)可能存在会话回收机制,加上后台状态下Cookie更新不及时,容易导致PHP会话过期,后端因$_SESSION['uid']为空无法执行SQL更新。
  3. 前端依赖风险:直接使用DOM元素的innerText作为耗时数据,一旦后台节流导致计时偏差,数据准确性无法保证;且无请求重试机制,失败后直接丢失数据。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:12:31