应使用HTTP头禁用缓存并添加随机参数,同时用安全方式获取真实IP记录日志:在image.php中设置Cache-Control和Expires头,输出PNG前写入制表符分隔的日志,确保GD扩展启用、文件无BOM、日志路径可写且并发安全。

PHP生成带时间戳的图片并强制刷新
浏览器缓存会导致图片不更新,即使PHP脚本每次生成新内容。关键不是重命名文件,而是控制HTTP头让浏览器不复用缓存。
用 header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0') 和 header('Expires: 0') 强制禁用缓存;再加一个随机查询参数(如 ?t=)可进一步规避代理或CDN缓存。
示例:在 image.php 中输出PNG时,先发头再输出图像数据:
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Expires: 0');
imagepng($im);
imagedestroy($im);
记录访问时间与IP到日志文件
每次图片被请求,就写入一条日志。注意避免并发写入冲突,fopen(..., 'a') 是安全的追加模式,但需确保目录可写且磁盘不爆满。
立即学习“PHP免费学习笔记(深入)”;
获取真实IP要小心:$_SERVER['REMOTE_ADDR'] 在无代理时可用;若有Nginx+PHP-FPM,通常需检查 $_SERVER['HTTP_X_FORWARDED_FOR'] 或 $_SERVER['HTTP_X_REAL_IP'],但必须验证来源可信(比如只信任内网反向代理IP),否则容易被伪造。
日志建议用制表符分隔,便于后续用awk/grep处理:
$ip = $_SERVER['REMOTE_ADDR'];
if (!empty($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP)) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
$log = date('Y-m-d H:i:s') . "\t" . $ip . "\t" . $_SERVER['REQUEST_URI'] . "\n";
file_put_contents('/var/log/php-image-access.log', $log, FILE_APPEND | LOCK_EX);
把图片生成和日志写入封装成独立脚本
不要把日志逻辑混进前端页面或框架路由里——图片URL应直接指向一个轻量PHP脚本(如 /img/tick.php),它只做两件事:记录、绘图、输出。这样性能可控,也方便Nginx单独限速或屏蔽恶意刷图。
常见疏漏点:
-
imagecreatetruecolor()后忘记调用imagecolorallocate()分配颜色,导致黑图 - 没检查GD扩展是否启用(
extension=gd在php.ini中),报错是Call to undefined function imagecreate() - 日志路径写错权限,比如写到
/var/log/但PHP运行用户(如www-data)无写权限 - 用
date('i:s')当作唯一标识,结果同一秒多次请求日志覆盖(实际不需要唯一性,但别误以为能靠它查单次请求)
调试时怎么看日志是否生效
别等上线后翻大日志文件。开发阶段加一句 error_log("logged: " . $log);,然后 tail -f /var/log/php_errors.log 实时盯住;或者临时把日志写到当前目录下(如 ./debug.log),确认格式和时机对了再切回系统路径。
如果图片显示空白但无报错,多半是输出了额外空格或BOM——检查PHP文件是否UTF-8无BOM编码,且开头没有空行或前的空白。
日志字段顺序和分隔符一旦定下,就别轻易改。后面用脚本分析时,cut -f2 就是IP,awk '{print $1}' 就是时间,改了就得同步修所有下游解析逻辑。











