gin.staticfs 默认不设置缓存头,需手动配置强缓存(cache-control)或协商缓存(etag/last-modified);磁盘文件推荐用 http.fileserver 实现自动协商,embed.fs 则需手动设 cache-control 和 etag,并通过文件名哈希更新资源。

gin.StaticFS 默认不带缓存头,必须手动干预
直接用 gin.StaticFS 或 router.Static 挂载静态资源时,Gin 会调用底层 http.FileServer,但默认不设置任何 Cache-Control、ETag 或 Last-Modified。浏览器每次请求都走完整流程,毫无缓存效果。
这不是 Gin 的 bug,而是设计使然:缓存策略需由业务决定,框架不替你做假设。想强缓存或协商缓存,得自己加中间件或替换 handler。
- 强缓存依赖
Cache-Control: public, max-age=N(或immutable) - 协商缓存依赖
ETag或Last-Modified+ 客户端带If-None-Match/If-Modified-Since -
http.ServeFile和http.FileServer原生支持协商缓存,但只在文件系统路径可 stat 且未被包装时才生效
用 http.FileServer 替代 gin.Static 实现协商缓存
如果你的静态资源是磁盘上真实文件(如 ./public/js/app.js),别用 router.Static,改用标准库 http.FileServer 并确保路径干净:
fs := http.FileServer(http.Dir("./public"))
router.NoRoute(func(c *gin.Context) {
// 拦截 /static/ 开头的请求
if strings.HasPrefix(c.Request.URL.Path, "/static/") {
c.Request.URL.Path = strings.TrimPrefix(c.Request.URL.Path, "/static/")
fs.ServeHTTP(c.Writer, c.Request)
return
}
c.AbortWithStatus(404)
})
这样能触发 http.FileServer 内置的协商逻辑:自动计算 ETag(基于文件内容哈希)、设置 Last-Modified(基于 os.FileInfo.ModTime()),并响应 304 Not Modified。
- 不要把
http.Dir包进gziphandler.GzipHandler之类中间件再传给FileServer——会破坏io.ReadSeeker接口,导致协商失效 - 确保文件系统支持
Stat(),NFS/FUSE 挂载卷可能返回固定时间戳,让Last-Modified失效 - 如果用 embed.FS,它不实现
os.FileInfo,也无法生成有效ETag,只能退到强缓存
对 embed.FS 或内存资源强制加强缓存头
当静态资源来自 embed.FS 或生成式内容(如 SVG 图标模板),无法靠 FileServer 自动协商,只能走强缓存:
func staticEmbedHandler(fs embed.FS, prefix string) gin.HandlerFunc {
return func(c *gin.Context) {
path := strings.TrimPrefix(c.Request.URL.Path, prefix)
if path == "" || path == "/" {
c.AbortWithStatus(404)
return
}
data, err := fs.ReadFile(path)
if err != nil {
c.AbortWithStatus(404)
return
}
// 强缓存:public + long max-age + immutable(防刷新重发)
c.Header("Cache-Control", "public, max-age=31536000, immutable")
c.Header("ETag", fmt.Sprintf(`"%x"`, md5.Sum(data)))
c.Data(200, mime.TypeByExtension(path), data)
}
}
// 使用:router.NoRoute(staticEmbedHandler(myEmbedFS, "/assets/"))
-
immutable很关键:告诉浏览器即使用户点刷新也不重发请求,避免重复校验 -
ETag手动生成(如用md5或sha256),否则浏览器无法比对变更 - 别漏掉
Content-Type,c.Data()不自动推断,要用mime.TypeByExtension() - 不要在 handler 里调
c.Next()或嵌套其他中间件——静态响应必须早于路由链执行
强缓存下如何安全更新资源?
强缓存 max-age=31536000 意味着一年内浏览器不会重新拉取,所以你不能靠改文件内容生效,必须改 URL:
- 构建时在文件名后加 hash:
app.a1b2c3d4.js→ URL 变更,缓存自然失效 - 用 query 参数(不推荐):
app.js?v=a1b2c3d4,部分代理会忽略 query 缓存 - CDN 场景下,配合
Cache-Control: s-maxage控制边缘节点缓存周期 - HTML 页面本身不能强缓存太久,否则引用的新 JS/CSS 路径不会更新——首页建议设
max-age=3600或用 ETag
最常被忽略的是 HTML 文件的缓存控制:它才是资源入口,一旦它被强缓存且没更新,后面所有带 hash 的 JS/CSS 都白搭。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











