直接读取视频文件访问日志不靠谱,因预加载、CDN缓存、断点续播等会产生大量206请求,单次播放可能记录5–20条日志,且无法识别是否播完,也缺乏用户ID、设备、时长等业务维度。

为什么直接读取视频文件访问日志不靠谱
很多开发者第一反应是“把 video.mp4 放在 Web 目录下,靠 nginx/apache 日志统计播放次数”,这看似简单,但实际漏统严重:浏览器预加载、CDN 缓存、断点续播、用户拖拽进度条都会触发多次 206 Partial Content 请求,单次播放可能产生 5–20 条日志;而真正完成播放的用户,日志里根本看不出“播完没”。更麻烦的是,日志里没有用户 ID、设备类型、播放时长等业务需要的维度。
用 php 接收前端播放事件上报(推荐方案)
核心思路:前端在关键节点(开始播放、暂停、结束、异常中断)调用 PHP 接口上报行为,PHP 写入数据库。这样数据可控、可关联用户、可扩展字段。
- 前端需监听
play、pause、ended、Error等原生事件,用fetch()或XMLHttpRequest发送 POST 请求到 PHP 接口 - PHP 接口(如
/api/log_play.php)需校验Content-Type: application/json、解析file_id、user_id、Event("start"/"pause"/"end")、timestamp、duration(当前已播秒数) - 务必加基础防刷:限制同一
user_id+file_id在 1 秒内只记 1 条start;对end事件可设最低时长阈值(如 ≥ 30 秒才算有效播放)
$_POST = json_decode(file_get_contents('php://input'), true); if (!isset($_POST['file_id'], $_POST['user_id'], $_POST['event'])) { http_response_code(400); exit; } // 示例:写入 MySQL(请用 PDO 预处理防注入) $stmt = $pdo->prepare("INSERT INTO video_logs (file_id, user_id, event, timestamp, duration) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$_POST['file_id'], $_POST['user_id'], $_POST['event'], time(), (int)$_POST['duration'] ?? 0]);
怎么避免重复统计和漏统计
真实场景中,用户切后台、网络中断、页面刷新都会导致事件丢失或错乱。不能只依赖前端上报。
- 服务端兜底:对每个
file_id+user_id维护一个临时状态表(如video_play_sessions),记录started_at时间;若 10 分钟内无end上报,定时任务扫描并标记为“未完成” - 前端防重:用
sessionStorage存一个唯一session_id,每次上报带上;PHP 接口检查该session_id是否已记录过start,避免刷新页面重复计数 - 区分“播放”和“完成”:不要把
play事件直接当“一次播放”——用户点一下又关掉不算;建议以ended或duration >= total_duration * 0.9作为有效完成依据
CDN 场景下如何获取真实用户 IP 和地域
如果视频资源走 CDN,且统计要按地域分析,PHP 默认的 $_SERVER['REMOTE_ADDR'] 是 CDN 节点 IP,不是用户真实 IP。
立即学习“PHP免费学习笔记(深入)”;
- 检查 CDN 是否透传真实 IP:常见 Header 有
X-forwarded-For、X-Real-IP、Cf-Connecting-Ip(Cloudflare) - PHP 中应优先取
$_SERVER['HTTP_X_FORWARDED_FOR'](注意该字段可伪造,需结合白名单验证 CDN 回源 IP) - 地域识别别用纯 PHP 库(如
geoip扩展已废弃),改用离线 GeoLite2 City 数据库 +maxmind-db/readercomposer 包,查 IP 更准
复杂点在于:播放统计不是“一次请求一个结果”,而是多点埋点+状态补全+业务规则过滤。最容易被忽略的是“用户中途关闭页面导致 ended 事件根本发不出”,必须靠服务端 session 超时机制兜底。