必须同时在Gin层拦截OPTIONS并由Nginx缓存,因浏览器仅信任Access-Control-Max-Age头决定预检缓存时长,而Gin默认不设该头或未加always参数,且Nginx默认不缓存OPTIONS请求,需显式配置proxy_cache_methods、proxy_cache_key、proxy_cache_valid及add_header Access-Control-Max-Age ... always。

直接在 Gin 层拦截并返回 OPTIONS 响应,同时配 Nginx 缓存,才能真正解决预检慢——光靠后端 return 204 不够,不缓存就等于每次都要走完整链路。
为什么 Gin 的 OPTIONS 中间件返回快但浏览器仍频繁触发?
因为浏览器只信任 Access-Control-Max-Age 头决定缓存时长,而 Gin 默认不设这个头,或设了但没加 always 参数(Nginx 场景下尤其关键)。即使 Gin 中间件写了 c.Header("Access-Control-Max-Age", "86400"),若请求被 proxy_pass 到 upstream,该 header 可能被后端覆盖或丢失;更常见的是:Gin 返回了,但 Nginx 没把它当缓存对象处理,导致每次请求都穿透到 Gin。
- Gin 自身无法控制浏览器缓存行为,必须由响应头中的
Access-Control-Max-Age和Cache-Control共同生效 - 若你用 Nginx 反代 Gin,OPTIONS 请求默认不进
proxy_cache,因为proxy_cache_methods默认不含OPTIONS - 即使 Gin 返回了 204 + 正确 CORS 头,若 Nginx 没开启缓存或 key 构造错误,依然无法 HIT
Nginx location 块中必须写的四条核心配置
不是加几个 add_header 就完事。以下配置需放在具体 API 路径的 location /api/ { ... } 内,且顺序不能乱:
-
proxy_cache_methods GET HEAD POST OPTIONS;—— 不加这句,Nginx 根本不把 OPTIONS 当可缓存请求 -
proxy_cache_key "$scheme$request_method$host$uri$is_args$args$http_origin";—— 必须含$http_origin,否则多源共用一个缓存项,A 域名的预检结果可能被 B 域名误用 -
proxy_cache_valid 204 86400;—— 预检绝大多数返回204 No Content,只对这个状态码设有效期才精准 -
add_header Access-Control-Max-Age 1728000 always;——always是硬性要求,否则return 204后 header 不生效
Gin 中间件里 OPTIONS 处理的两个雷区
很多人在 cors.go 里写了个 if method == "OPTIONS" { c.AbortWithStatus(204) } 就以为搞定了,其实埋了坑:
- 没显式调用
c.Header("Access-Control-Max-Age", "1728000")—— 浏览器收不到该头,缓存失效 - 没配
c.Header("Cache-Control", "public, max-age=1728000")—— CDN 或中间代理可能忽略Access-Control-Max-Age - 用了
c.Next()或后续中间件继续执行 —— OPTIONS 响应不该再走业务逻辑,c.AbortWithStatus(204)后必须return
正确写法示例(片段):
if c.Request.Method == "OPTIONS" {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
c.Header("Access-Control-Max-Age", "1728000")
c.Header("Cache-Control", "public, max-age=1728000")
c.AbortWithStatus(204)
return
}
验证是否真缓存成功的关键指标
别只看浏览器 Network 面板有没有 204,重点查三处:
- 响应头是否有
X-Cache-Status: HIT(Nginx 添加了add_header X-Cache-Status $upstream_cache_status;) - 响应头是否同时存在
Access-Control-Max-Age: 1728000和Cache-Control: public, max-age=1728000 - 用
curl -X OPTIONS -I http://your-api.com/api/xxx多次执行,观察Age响应头是否递增(说明缓存命中并老化)
最容易被忽略的是:Nginx 缓存区(proxy_cache_path)没配或路径不可写,会导致所有 HIT 变成 MISSED,但日志里未必报错。


















