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

如何基于阅读时长判断用户是否读完帖子并记录至数据库

需求:实现防刷阅读量的阅读记录系统

需要实现的功能:仅当用户未读过该帖子,且在页面停留时长达到帖子标注的阅读时长(比如5分钟)时,才将用户ID与帖子ID存入数据库,避免用户刷阅读量。


尝试过的方案及问题

方案1:使用PHP sleep函数

问题:直接阻塞服务端进程,导致页面长时间加载,完全无法满足需求。
代码示例:

$seconds = $time *60000; // 注:此处逻辑错误,sleep接收的是秒数而非毫秒
if($post_data){
  $check = new post();
   $result = $check->checkreader($userid,$articleid);
   if($result){
// 用户已读过该帖子
   }else{
    sleep($seconds);
    $check->addreader($id, $articleid);
  } 

说明:PHP是服务端脚本,sleep()会让服务端进程挂起,用户页面会一直处于加载状态,体验极差;且用户一旦关闭页面,后续入库操作直接中断,完全不符合业务逻辑。

方案2:使用JavaScript setTimeout方法

问题:延迟逻辑完全不生效,入库操作会在服务端直接执行。
代码示例:

$seconds = $time * 60000;// 分钟转毫秒
if($post_data){
  $check = new post();
   $result = $check->checkreader($userid,$articleid);
   if($result){
// 用户已读过该帖子
   }else{
    echo "<script>
     setTimeout(function(){".
      $check->addreader( $id, $articleid)
     .";}, $seconds);
    </script>";
}

说明:PHP代码在页面输出前就会执行完毕,$check->addreader()会直接在服务端调用,前端的setTimeout只是输出了一个已经执行过的结果,根本起不到延迟入库的作用。


可行实现方案

核心思路

采用前端计时+后端二次校验的组合方式,既保证用户停留时长达标,又通过后端校验避免作弊。

具体步骤

1. 页面加载时的服务端初始校验

服务端先判断用户是否已读该帖子,未读则把需停留时长传给前端:

if($post_data){
    $check = new post();
    $hasRead = $check->checkreader($userid, $articleid);
    if($hasRead){
        // 用户已读,无需后续操作
        exit;
    }
    // 将帖子要求的阅读时长转成秒,传给前端
    $requiredDuration = $post_data['required_read_minutes'] * 60;
    echo "<script>const requiredDuration = {$requiredDuration};</script>";
    // 记录页面加载时间,用于后端校验停留时长
    $_SESSION["article_load_{$articleid}"] = time();
}

2. 前端计时与页面状态监听

前端用JS计时,同时监听页面可见状态,避免用户切后台挂时长:

let timer = null;
let stayedTime = 0;
let isPageVisible = true;

// 监听页面是否在前台
document.addEventListener('visibilitychange', () => {
    isPageVisible = !document.hidden;
    if (!isPageVisible) {
        // 切后台时暂停计时
        clearInterval(timer);
    } else {
        // 切回前台继续计时
        startTimer();
    }
});

function startTimer() {
    timer = setInterval(() => {
        if (isPageVisible) {
            stayedTime++;
            // 达到要求时长,请求后端入库
            if (stayedTime >= requiredDuration) {
                clearInterval(timer);
                fetch('/api/save-reader-record', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
                    },
                    body: JSON.stringify({
                        user_id: '<?php echo $userid; ?>',
                        article_id: '<?php echo $articleid; ?>'
                    })
                }).then(res => res.json())
                  .then(data => {
                      if (data.success) {
                          console.log('阅读记录已保存');
                      }
                  });
            }
        }
    }, 1000); // 每秒计数一次
}

// 页面加载完成后启动计时
window.addEventListener('load', startTimer);

3. 后端接口的二次校验

后端接口必须再次校验,防止用户直接调用接口刷记录:

// 示例接口逻辑,适配任意PHP框架
function saveReaderRecord() {
    $userId = $_POST['user_id'];
    $articleId = $_POST['article_id'];

    // 校验1:用户是否已读该帖子
    $postModel = new post();
    if ($postModel->checkreader($userId, $articleId)) {
        echo json_encode(['success' => false, 'msg' => '已读过该帖子']);
        exit;
    }

    // 校验2:实际停留时长是否达标
    $loadTime = $_SESSION["article_load_{$articleId}"] ?? 0;
    $requiredTime = $postModel->getRequiredReadTime($articleId) * 60; // 转成秒
    if ((time() - $loadTime) < $requiredTime) {
        echo json_encode(['success' => false, 'msg' => '停留时长不足']);
        exit;
    }

    // 校验通过,添加阅读记录
    $postModel->addreader($userId, $articleId);
    echo json_encode(['success' => true]);
}

额外防刷优化点

  • 禁用页面刷新重复计时:页面加载时生成唯一会话标识,后端记录该标识,避免同一用户同一页面多次触发计时。
  • 监听用户交互:添加鼠标移动、滚动事件监听,只有用户有交互时才累计时长,避免开着页面不动挂时长。
  • 未登录用户校验:用IP+浏览器指纹做标识,防止同一IP刷阅读量。

内容的提问来源于stack exchange,提问作者Junior

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 10:15:00