thinkphp控制器需手动实现last-modified缓存协商:先生成gmt格式时间戳,再设置响应头,最后校验if-modified-since并主动返回304。漏判请求头、时间精度错误或时区不统一是常见失败原因。

ThinkPHP控制器本身不自动设置 Last-Modified 响应头,也没有内置接口(如 Spring 的 LastModified)供你直接实现。必须手动计算时间戳、判断请求头、决定是否返回 304 —— 否则浏览器永远收不到缓存协商响应。
如何在 ThinkPHP 控制器中手动设置 Last-Modified 并校验
核心是三步:生成合法 GMT 时间戳 → 设置响应头 → 检查 If-Modified-Since 并提前退出。ThinkPHP 的 response() 支持链式设置 header,但不会帮你做条件判断。
-
Last-Modified必须是 GMT 格式、精确到秒,例如Sun, 06 Apr 2026 08:48:00 GMT;用gmdate('D, d M Y H:i:s', $timestamp) . ' GMT'生成,不能用date() - 需主动读取
$_SERVER['HTTP_IF_MODIFIED_SINCE'],并用strtotime()转为时间戳后比对(注意:浏览器发送的值可能带时区或微秒,strtotime()能安全处理) - 若比对通过,必须立即调用
header('HTTP/1.1 304 Not Modified')并exit,否则后续逻辑仍会执行并覆盖状态码 - ThinkPHP 6 的
response()->header()不会阻止后续输出,它只是“准备发”,真正拦截靠你自己控制流程
常见错误:为什么设置了 Last-Modified 却还是 200?
根本原因不是 header 没设上,而是没做校验逻辑。很多开发者只写:
return response($data)->header('Last-Modified', $gmtTime);
这只会让响应带一个头,但浏览器下次带 If-Modified-Since 来时,服务端毫无反应,照样返回 200 + 全量内容。
- 漏判
$_SERVER['HTTP_IF_MODIFIED_SINCE']:这是最常踩的坑,以为设了头就等于启用了协商缓存 - 时间戳精度错误:数据库字段或文件修改时间用毫秒(如
microtime(true)),但Last-Modified只接受秒级,会导致比对失败 - 未统一时区:用
date()生成 GMT 字符串,结果输出的是本地时区时间(比如 CST),违反 HTTP 规范,部分客户端直接忽略该头 - 在 JSON 接口里混用
json()和手动header():ThinkPHP 的json()方法内部会调用response()并设Content-Type,但不会覆盖你之前设的Last-Modified;可安全组合,但 304 判断仍要自己写
结合资源场景选时间戳来源
时间戳不能拍脑袋写 time(),得反映真实“最后变更”语义,否则缓存会失效或误命。
- 静态模板页(如帮助中心):用模板文件修改时间
filemtime(THINK_PATH . 'template/help.html') - 数据库内容页(如文章详情):查对应记录的
update_time字段,转为时间戳,**不要用当前时间** - 聚合接口(如用户+订单+通知):取多个数据源中最大的更新时间戳,避免因某子项未更新导致整体缓存过期
- 动态计算结果(如报表):若无明确变更点,建议不用
Last-Modified,改用ETag(如md5(serialize($data))),更可靠
最关键的细节是:304 响应体必须为空,且不能有任何输出(包括空格、BOM、echo、var_dump)。ThinkPHP 的调试模式如果开启,debug 信息可能在 304 后追加输出,导致响应损坏 —— 生产环境务必关掉 app_debug 或在 304 分支前确保输出缓冲已清空。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










