Laravel可通过中间件或控制器在响应中动态设置ETag或Last-Modified头实现协商缓存:1. ETag推荐用内容SHA-256哈希或数据库version字段生成;2. Last-Modified须用date(DATE_RFC7231, $timestamp)格式化;3. 需在路由分发后、响应发送前校验If-None-Match或If-Modified-Since并返回304。

你需要让PHP框架发出的HTTP响应自动携带ETag或Last-Modified头,使客户端(浏览器或CDN)能在资源未变时直接复用缓存、跳过完整响应体传输,从而降低带宽消耗和服务器压力。这不需要改前端代码,也不依赖外部缓存服务,只需在响应生成环节插入两行逻辑。
给响应动态生成ETag
方法一:基于响应内容计算强ETag
在控制器返回Response对象前,对原始响应体做SHA-256哈希,拼接成双引号包裹的强ETag值,写入headers:
```php
$body = $response->getContent();
$etag = '"' . hash('sha256', $body) . '"';
$response->headers->set('ETag', $etag);
```
立即学习“PHP免费学习笔记(深入)”;
这一步必须在 setContent() 之后、send() 之前执行,否则 getContent() 返回空字符串,ETag会恒为同一值——【所有后续缓存校验都会失效】。
方法二:用数据库记录版本号生成弱ETag
若响应体来自数据库查询,且该表有 updated_at 或 version 字段,可直接取其值构造弱ETag:
$etag = 'W/"' . $record['version'] . '"';
$response->headers->set('ETag', $etag);
弱ETag开头带 W/ 前缀,语义是“可能不精确相等”,适合内容可能被压缩或编码转换的场景。
设置Last-Modified时间戳
第一步:从数据源提取最后修改时间
若响应对应单条数据库记录,优先使用该记录的 updated_at 字段;若为聚合结果,取其中最新的一条 updated_at;若无此字段,回退到文件mtime(如模板文件):
$lastModified = $record['updated_at'] ?? @filemtime($templatePath);
第二步:格式化为RFC 7232标准时间字符串
必须用 date(DATE_RFC7231, $timestamp),不能用 DATE_RFC2822 或自定义Y-m-d H:i:s——【非标准格式会导致If-Modified-Since校验被忽略】。
第三步:写入响应头并禁用内容编码干扰
$response->headers->set('Last-Modified', date(DATE_RFC7231, $lastModified));
// 避免gzip压缩改变响应体导致ETag失效,此处可选:
// $response->headers->remove('Content-Encoding');
在请求入口拦截304校验
① 获取客户端发来的 If-None-Match 和 If-Modified-Since 头:
$ifNoneMatch = $request->headers->get('If-None-Match');
$ifModifiedSince = $request->headers->get('If-Modified-Since');
② 若两者皆为空,跳过校验,正常返回响应。
③ 若 If-None-Match 存在且与当前ETag完全匹配(注意处理多个ETag逗号分隔的情况),立即返回304响应:
if ($ifNoneMatch && $this->matchesEtag($ifNoneMatch, $currentEtag)) {
return new Response('', 304);
}
④ 若 If-Modified-Since 存在且早于资源最后修改时间,也返回304:
if ($ifModifiedSince && strtotime($ifModifiedSince) >= $lastModified) {
return new Response('', 304);
}
这四步必须放在路由分发之后、控制器执行之前,否则无法提前终止响应流程。Laravel可在中间件中实现,Symfony可用EventSubscriber监听kernel.response事件反向注入校验逻辑。



















