last-modified在秒内多次修改时失效,因其时间精度仅到秒级,同一秒内修改不更新时间戳,浏览器比对if-modified-since后误判未变而返回304;etag基于内容哈希(如md5),字符级变更即触发新标识,天然支持毫秒级识别,配合cache-control可实现分钟级生效。

ETag 和 If-None-Match 通过内容哈希值实现秒级甚至毫秒级变更识别,核心在于“内容决定标识”,而非依赖时间戳这类粗粒度字段。
为什么 Last-Modified 在秒内多次修改时会失效
Last-Modified 精度仅到秒——同一秒内无论文件改多少次,时间戳不变;浏览器发 If-Modified-Since 时也只带秒级时间,服务器比对后认为“没变”,直接返回 304,导致用户看到过期内容。这在热更新、CI/CD 自动部署或高频配置变更场景中很常见。
而 ETag 若基于内容哈希(如 xxHash、MD5 或 SHA-256),哪怕一个字符变动,哈希值就完全不同,天然规避了时间精度缺陷。
内容哈希如何生成并确保一致性
- 构建阶段预计算:打包工具(如 Webpack/Vite)在生成 JS/CSS/图片时,对文件内容做哈希,把哈希值嵌入文件名(如
app.a1b2c3d4.js)或写入响应头ETag: "a1b2c3d4" - 运行时动态计算:Nginx 配合
ngx_http_fingerprint_module对响应体实时哈希;Apache 则需后端(如 Node.js/Java)在返回前计算并设ETag响应头 - 关键原则:禁用默认 ETag(如 Nginx 的
etag off;,Apache 的FileETag None),避免 inode/mtime 等不稳定因子干扰
协商过程如何体现“秒级识别”
浏览器缓存资源后,下次请求自动带上 If-None-Match: "old-hash"。服务端拿到该值,立即对当前资源内容重新哈希,得到 "new-hash":
- 若
"new-hash" === "old-hash"→ 内容未变 → 返回 304,不传 body - 若
"new-hash" !== "old-hash"→ 即使距上次修改不到 1 秒 → 返回 200 + 新内容
整个过程不依赖系统时钟同步、不等待下一秒,只要内容有异,下一次协商请求就能立刻感知。
配合 Cache-Control 才能真正落地“分钟级校验”
ETag 本身不触发请求,只是验证机制。要让浏览器频繁发起协商,得靠缓存策略驱动:
- 设
Cache-Control: public, max-age=60, must-revalidate,强制每分钟过期一次 - 过期后浏览器自动带
If-None-Match发起协商,而不是直接复用缓存 - 这样,即使资源在第 59 秒更新,第 60 秒的请求就能命中新内容,实现“分钟级生效”











