如何准确计算用户在网页上的停留时间

本文详解如何在 php 中通过登录/登出时间戳差值,精确累计用户页面停留总时长(单位:秒),并安全更新数据库,同时提供可读性高的时间格式化输出。

在 Web 应用中,统计用户真实页面停留时间是用户行为分析的关键指标。核心逻辑应为:本次会话停留时长 = 登出时刻时间戳 − 登录时刻时间戳,再将该时长(秒)累加至用户历史总时长字段中。原始代码存在多处关键问题,需逐一修正:

✅ 关键问题与修复说明

  1. 时间字段类型不匹配
    time_on_page 字段在数据库中必须为 INT 类型(非 DATETIME),用于存储累计秒数。原始代码误将 $data(一个格式化后的日期字符串)传入 TIMESTAMPDIFF(),导致 SQL 语法错误且逻辑混乱。

  2. 错误的二次时间转换
    原始代码中:

    $actual_date = date("Y-m-d H:i:s",$result['time_on_page']); // ❌ 错误:把秒数当时间戳转成日期
    $calculate_time_on_page = $total + strtotime($actual_date); // ❌ 再次 strtotime() 无意义且易出错

    正确做法是:$result['time_on_page'] 本身就是整型秒数,直接相加即可:

    $calculate_time_on_page = $total + $result['time_on_page']; // ✅ 纯数值累加
  3. SQL 注入风险
    原始代码直接拼接 $id 变量,极易引发 SQL 注入。应严格使用预处理语句绑定参数。

  4. 冗余与无效 SQL
    UPDATE ... TIMESTAMPDIFF(SECOND, '...', '...') 是错误用法——TIMESTAMPDIFF 需两个有效 datetime 字符串,而此处 '...' 是动态生成的非法时间格式。正确方式是直接存整型秒数。

✅ 优化后的完整实现(含安全防护与健壮性)

public function calculateTimeOnPage()
{
    // 1. 获取用户ID并校验会话
    if (!isset($_SESSION['userid'])) {
        throw new Exception("User not logged in.");
    }
    $id = (int)$_SESSION['userid']; // 强制整型过滤

    // 2. 查询用户登录起始时间与当前累计时长
    $stmt = $this->connection->pdo->prepare("SELECT page_start_time, time_on_page FROM users WHERE id = ?");
    $stmt->execute([$id]);
    $result = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$result) {
        throw new Exception("User not found.");
    }

    // 3. 计算本次会话时长(秒)
    $end_time = date('Y-m-d H:i:s');
    $start_time = $result['page_start_time']; // 格式如 '2025-05-20 14:30:22'

    $current_session_seconds = strtotime($end_time) - strtotime($start_time);
    if ($current_session_seconds < 0) {
        $current_session_seconds = 0; // 防止时钟回拨或异常
    }

    // 4. 累加总时长(秒)
    $total_seconds = (int)$result['time_on_page'] + $current_session_seconds;

    // 5. 安全更新:使用预处理防止SQL注入
    $stmt_up = $this->connection->pdo->prepare(
        "UPDATE users SET time_on_page = ? WHERE id = ?"
    );
    $stmt_up->execute([$total_seconds, $id]);

    // 6. 格式化输出可读时长(示例:2 hours, 15 minutes and 30 seconds)
    echo "Total time on page: " . $this->convertSecToTime($total_seconds) . "
"; } // 提取为独立方法,便于复用 private function convertSecToTime($sec) { if ($sec <= 0) return "0 seconds"; $date1 = new DateTime("@0"); $date2 = new DateTime("@$sec"); $interval = $date1->diff($date2); // 更推荐用 DateTime::diff() $parts = [ 'y' => 'years', 'm' => 'months', 'd' => 'days', 'h' => 'hours', 'i' => 'minutes', 's' => 'seconds' ]; $formatted = []; foreach ($parts as $unit => $label) { $value = $interval->$unit; if ($value > 0) { $label = ($value === 1) ? rtrim($label, 's') : $label; $formatted[] = "$value $label"; } } if (empty($formatted)) return "0 seconds"; if (count($formatted) === 1) return $formatted[0]; $last = array_pop($formatted); return implode(', ', $formatted) . ' and ' . $last; }

⚠️ 重要注意事项

  • 数据库字段设计:确保 users.time_on_page 为 INT UNSIGNED,足够容纳多年累计秒数(如 INT(10) 最大支持约 34 年)。
  • 登录时写入 page_start_time:务必在用户登录成功后,用 date('Y-m-d H:i:s') 精确记录起始时间到该字段。
  • 登出触发调用:此方法应在用户主动登出或会话超时销毁前调用,避免遗漏。
  • 前端补充(可选):为防用户关闭浏览器未触发登出,可结合 beforeunload 事件发送心跳请求,但服务端仍以登出操作为准。

通过以上重构,你将获得一个安全、准确、可维护的页面停留时间统计方案。