flock()是php文件计数器的底线,不加锁高并发下必丢数;$_server['http_referer']不可信需过滤转义;uv不能仅靠session,应组合cookie、ip+ua哈希;小站宜用nginx日志+定时解析替代实时写库。

flock() 是 PHP 文件计数器能用的底线,不加锁就写 counter.txt,高并发下必然丢数。
为什么直接 fwrite() 会丢访问量
两个请求几乎同时执行 file_get_contents('counter.txt'),都读到 100;各自加一变成 101,再先后写回——最终文件里还是 101,而不是正确的 102。这种竞态不是“偶尔出错”,而是在真实流量下稳定复现。
解决它只有一个可靠路径:用 flock($fp, LOCK_EX) 强制串行化写操作。注意三点:
-
fopen()必须用'r+'模式(不能是'w+',否则清空文件) - 写入前必须
rewind($fp),否则新内容追加在末尾,旧数字残留 - 写完要
ftruncate($fp, ftell($fp)),截断多余字节,避免1010这类脏数据
$_SERVER['HTTP_REFERER'] 解析来源时的坑
这个字段由浏览器提供,不可信、可为空、可伪造。直接 INSERT INTO logs (referer) VALUES ('$_SERVER[HTTP_REFERER]') 会报 SQL 错误或被注入。
安全做法是:
- 先判断是否存在:
$referer = $_SERVER['HTTP_REFERER'] ?? 'direct'; - 过滤非法字符:
filter_var($referer, FILTER_SANITIZE_URL) - 入库前转义:
mysqli_real_escape_string($conn, $referer)或用 PDO 预处理 - 别依赖它做关键分流——UTM 参数(如
?utm_source=wechat)才真正可控
独立访客(UV)不能只靠 session_start()
很多人写 if (!$_SESSION['counted']) { increment(); $_SESSION['counted'] = true; },但这只对单页面有效。用户跳转到其他 PHP 页面时,session 不自动跨页延续,除非每页都调用 session_start(),且确保 session 存储路径可写、cookie 未被拦截。
更现实的做法是组合识别:
- 优先取
$_COOKIE['uv_id'](前端 JS 生成并设 30 天过期) - fallback 到
$_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']哈希(注意 IPv6 地址长度变化) - 完全避开 IP + UA 组合去重——运营商 NAT 导致千人共 IP,会严重低估 UV
日志分析比实时写库更适合小站
与其每次请求都连 MySQL 插一条 visitor_logs,不如让 Nginx 把原始请求记进 /var/log/nginx/access.log,再用 PHP 定时跑一次解析脚本:
- 匹配 PHP 路径:
preg_match('/\s+\/[^?]+\.php(?:\?|$)/', $line, $m) - 按小时聚合:
date('Y-m-d H', strtotime($timestamp)) - 用
array_unique(array_column($ips, 'ip'))粗略算 UV(仅限低频站点) - 结果存 JSON 文件,前端直接
file_get_contents('stats.json')渲染,零数据库压力
真正的难点不在代码怎么写,而在于你是否清楚自己要的是「总 PV」、「今日 UV」,还是「首页跳出率」——不同目标,技术选型差得远。没想清这点,先写 flock() 也没用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











