PHP项目怎样实现资讯阅读时长统计?

wen PHP项目 57

本文目录导读:

PHP项目怎样实现资讯阅读时长统计?

  1. 方案一:基于“可见性”的定时上报(推荐,最准确)
  2. 方案二:前后端时间戳差值计算(简单粗暴)
  3. 方案三:后端日志 + 离线/异步分析(轻量级,适合高并发)
  4. 数据表设计建议
  5. 总结建议

在PHP项目中实现资讯阅读时长统计,核心思路是客户端行为追踪 + 服务端日志记录

因为PHP本身是无状态的(每次请求结束后变量销毁),所以无法像长连接一样实时计算用户读了多久,通常采用事件上报的机制来实现。

下面是行业主流且技术成熟的三种实现方案:

基于“可见性”的定时上报(推荐,最准确)

用户在浏览器中,页面会通过JS定时(如每5-10秒)向服务端发送心跳请求,服务端根据累积心跳计算时长。

实现步骤:

  1. 前端(JavaScript):

    • 使用 Page Visibility API 监听页面切换(用户切到其他标签页或最小化窗口时暂停计时)。
    • 使用 setIntervalt 秒向服务端上报一次当前状态(阅读中/暂停)。
    • 在页面关闭或切换时,发送最后一次上报请求(可用 navigator.sendBeacon 确保数据送达)。
    // 伪代码
    let timer = null;
    let articleId = <?php echo $articleId; ?>;
    function startTracking() {
        timer = setInterval(() => {
            navigator.sendBeacon('/api/log_read_time.php', JSON.stringify({
                article_id: articleId,
                event: 'heartbeat', // 心跳
                duration: 10 // 心跳间隔10秒
            }));
        }, 10000); // 每10秒上报一次
    }
    function stopTracking() {
        clearInterval(timer);
        // 最后上报一次,确保不丢数据
        navigator.sendBeacon('/api/log_read_time.php', JSON.stringify({
            article_id: articleId,
            event: 'leave',
            duration: 0
        }));
    }
    // 页面可见性变化时暂停/恢复
    document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
            stopTracking();
        } else {
            startTracking();
        }
    });
    // 页面加载开始计时
    startTracking();
    // 页面关闭前上报
    window.addEventListener('beforeunload', stopTracking);
  2. 后端(PHP):

    • 接收心跳请求,将用户ID、文章ID、阅读时长(心跳间隔)写入数据库。
    • 注意:这里需要判断用户是否“真正在阅读”,而不是挂机,可以结合用户鼠标/键盘事件(前端只在有交互时发送心跳)来过滤。
    // api/log_read_time.php
    <?php
    session_start();
    if (!isset($_SESSION['user_id'])) {
        exit;
    }
    $input = json_decode(file_get_contents('php://input'), true);
    $articleId = intval($input['article_id']);
    $event = $input['event'];
    $duration = intval($input['duration']); // 前端发来的本次阅读时长(秒)
    // 连接数据库
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    // 1. 写入阅读日志表(记录每次心跳)
    // 2. 或者直接累加到文章阅读总时长表
    $stmt = $pdo->prepare("INSERT INTO reading_log (user_id, article_id, duration, created_at) VALUES (?, ?, ?, NOW())");
    $stmt->execute([$_SESSION['user_id'], $articleId, $duration]);
    echo 'ok';

优点:数据颗粒度细,准确性高。 缺点:对服务器有一定请求压力(但10秒一次通常可以接受)。


前后端时间戳差值计算(简单粗暴)

前端记录进入页面的时间,离开时上报给后端,但这种方法非常容易被用户挂机刷时长,所以通常需要配合其他信号使用。

步骤:

  1. 文章页面加载时,前端获取服务器时间(或生成一个唯一标识)。
  2. 页面关闭时,前端提交“开始时间戳”和“结束时间戳”给后端。
  3. 后端计算差值。
// 页面加载时生成一个token,记录开始时间
$entryToken = md5(uniqid(rand(), true));
$_SESSION['read_start'][$entryToken] = time();
// 页面关闭时,用户请求 /api/end_read.php?token=xxx
$token = $_GET['token'];
$startTime = $_SESSION['read_start'][$token];
$duration = time() - $startTime; // 阅读时长秒数
unset($_SESSION['read_start'][$token]); // 清理
// 存入数据库

缺点:如果用户打开页面后不关(甚至挂一天),时长会被严重夸大。不推荐单独使用,可配合方案一使用。


后端日志 + 离线/异步分析(轻量级,适合高并发)

如果不要求实时显示阅读时长(比如只记录用户读了多久用于后台分析),可以只在文章页面埋一个“曝光”日志。

  • 结构log表中每行记录一条行为
  • 思路:记录用户进入文章页面的时间点,然后在Hive/Spark/ES中做离线聚合。
    • user_id, article_id, event (enter/scroll/leave), timestamp
  • PHP后端只负责写日志,不做计算,后续通过大数据平台或SQL分析。
    SELECT user_id, article_id, 
           SUM(timestamp_diff) as total_read_time
    FROM reading_log
    GROUP BY user_id, article_id;

优点:对PHP服务器压力最小,吞吐量高。 缺点:无法实时展示给用户看“您已阅读XX分钟”。


数据表设计建议

无论用哪种方案,数据库表结构可以这样设计:

CREATE TABLE user_reading_records (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL COMMENT '用户ID',
    article_id INT UNSIGNED NOT NULL COMMENT '资讯ID',
    read_duration INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '阅读时长(秒)',
    session_id VARCHAR(64) NOT NULL COMMENT '一次阅读会话标识',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
    INDEX idx_user_article (user_id, article_id),
    INDEX idx_article (article_id),
    INDEX idx_session (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

说明session_id 用于标识用户“从打开到关闭”的一次完整阅读,可以根据这个字段做去重。


总结建议

需求场景 推荐方案
需要准确统计(如作为付费或积分依据) 方案一(可见性+定时心跳)
简单的排行榜/展示,允许少量误差 方案三(异步日志)
给用户展示“您已阅读X分钟” 方案一(实时性)
纯后端分析,不要求实时 方案三(性能好)

避坑指南

  1. 防刷:服务端记录心跳时,判断相邻两次心跳时间戳的间隔是否合理(如不超过30秒)。
  2. 断点续读:如果用户关闭浏览器,之前的心跳已经记录,不需要额外处理。
  3. 匿名用户:如果站点不强制登录,可以用 cookie + session_id 来标记匿名用户。

抱歉,评论功能暂时关闭!