如何基于阅读时长判断用户是否读完帖子并记录至数据库
需求:实现防刷阅读量的阅读记录系统
需要实现的功能:仅当用户未读过该帖子,且在页面停留时长达到帖子标注的阅读时长(比如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
相关产品推荐
相关产品推荐

