自动清空的购物车:CloudFront 的 Cookie 前缀如何让 WooCommerce 销售归零
发布于 2026年6月18日

症状:弹窗购物车满的,购物车页面却是空的
一个 WooCommerce 商店运行在 CloudFront 后面,源站是一台 EC2 实例上的 nginx + WAF。顾客添加商品时,通过 AJAX 更新的侧边迷你购物车能正常显示该商品。但打开 /cart/ 或 /checkout/ 时,WooCommerce 却返回"您的购物车是空的"。
影响直接落在营收上:没有一个订单能完成。对于一个交易型商店来说,这不是 UX bug——这是业务停摆。
片面的诊断:找对了地方,却用错了理由
第一次诊断指向了 CloudFront 缓存——指向那里是对的。但对细节的解读在两个点上是错的,而这两点彻底改变了解决方案:
缓存策略判断错误:当时假设该分发使用的是托管的 Managed-CachingOptimized 策略。实际上是 UseOriginCacheControlHeaders-QueryStrings——它会遵从源站发送的 Cache-Control。如我们将看到的,这个区别至关重要。
为每个路径建立专用 behavior:提议为 /cart/、/checkout/ 和 /my-account/ 创建独立的 behavior 并标记为不可缓存。不可行:该套餐有 5 个 behavior 的上限,而且已全部用完。
CloudFront 缓存确实在问题的路径上。但修复方案不是创建更多 behavior——而是让源站说出缓存策略本就在倾听的那门语言。
根因,第一层:源站没有发送 Cache-Control
UseOriginCacheControlHeaders 策略会依据源站响应中的 Cache-Control 头来决定缓存什么、缓存多久。问题在于:nginx 在任何响应中都没有发送 Cache-Control——无论是静态资源还是页面。WordPress 默认也不会发送适合 CDN 的缓存头。
没有这个头,行为就变得不确定:CDN 可能决定缓存一个动态的购物车页面,把同一个响应发给不同的用户。但单凭这一点,仍然无法解释购物车为空。还差第二层。
根因,第二层:CloudFront Function 吃掉了会话 Cookie
有一个 CloudFront Function 运行在 viewer-request 事件上,作用是通过过滤 Cookie 来归一化缓存键——只保留对缓存有意义的 Cookie,丢弃其余的。白名单保留了前缀为 woocommerce_ 的 Cookie。
击垮一切的细节是:WooCommerce 的会话 Cookie 并不叫 woocommerce_session——它是 wp_woocommerce_session_HASH,带有 wp_ 前缀。它不匹配白名单。结果是:Function 在请求到达源站之前就移除了这个 HttpOnly 会话 Cookie。即使 nginx 收到了请求,WordPress 也看不到会话 Cookie,于是创建了一个全新的、空的会话。这就是为什么换页时购物车会"清空"。
// CloudFront Function (viewer-request) — 用于缓存键的 Cookie 白名单
// 之前:wp_woocommerce_session_ 这个 Cookie 不匹配
function keepCookie(name) {
return name.startsWith('woocommerce_');
}
// 真实的会话 Cookie 是:wp_woocommerce_session_1a2b3c...(wp_ 前缀)
// → 在到达源站前被移除 → WordPress 重建了一个空会话修复,第一层:在 nginx 中设置正确的 Cache-Control
在 WAF server block 的 location / 中,我们现在显式设置 Cache-Control——对交易型路由使用 no-store,并在存在会话/登录 Cookie 时绕过缓存:
# nginx(源站)— 按路由设置 Cache-Control + 按 Cookie 绕过
location / {
proxy_hide_header Cache-Control;
set $page_cache "public, s-maxage=3600, max-age=300";
# 交易型路由永不缓存
if ($request_uri ~* "^/(cart|checkout|my-account)") {
set $page_cache "no-store, no-cache, must-revalidate";
}
# 任何活动会话(登录或购物车)都绕过缓存
if ($http_cookie ~* "wordpress_logged_in_|woocommerce_session_|woocommerce_cart_hash") {
set $page_cache "no-store, no-cache, must-revalidate";
}
add_header Cache-Control $page_cache;
}对于静态资源,则相反——长时间、不可变的缓存,并去掉会损害 CDN 效率的 Vary 头:
# nginx(源站)— 静态资源
location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2?|ico)$ {
proxy_hide_header Vary;
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=31536000, immutable";
}为什么既按路径、又按 Cookie 检测?
按路径检测(/cart/、/checkout/、/my-account/)最为稳健:它保证这些路由永远不被缓存,无论顾客是否带有 Cookie。按 Cookie 检测则覆盖其余情况——任何由带有活动会话的用户查看的页面,都不应提供属于他人的缓存内容。
修复,第二层:把 wp_woocommerce_session_ 前缀加入白名单
对 CloudFront Function 的修复只有一行——把 wp_woocommerce_session_ 前缀加入要保留的 Cookie 白名单:
// CloudFront Function (viewer-request) — 修正后的白名单
function keepCookie(name) {
return name.startsWith('woocommerce_') ||
name.startsWith('wp_woocommerce_session_'); // <-- 真实的会话 Cookie
}注意:已发布的 CloudFront Function 是全局的——它会影响所有使用它的分发。该更改必须在上线前经过审查和验证,而不是在生产环境里临时硬试。
验证
nginx 配置测试与重载(在容器中运行):
sudo docker exec waf nginx -t
sudo docker exec waf nginx -s reloadCloudFront 失效——先对受影响的路由做一次清除,这样就不必等待旧的 TTL 过期:
aws cloudfront create-invalidation \
--distribution-id EXXXXXXXXXXXXX \
--paths "/cart/*" "/checkout/*" "/my-account/*"逐层的结果:
之前 → 之后:
/cart/ Cache-Control : (无) -> no-store, no-cache, must-revalidate
/checkout/ Cache-Control : (无) -> no-store, no-cache, must-revalidate
/my-account/ Cache-Control : (无) -> no-store, no-cache, must-revalidate
普通页面 Cache-Control : (无) -> public, s-maxage=3600, max-age=300
静态资源 Cache-Control : (无) -> public, max-age=31536000, immutable
wp_woocommerce_session_ Cookie : 被移除 -> 传递到源站
购物车可用 : 否 -> 是端到端验证了完整流程:添加商品 → /cart/ 显示商品。在响应头中得到确认:购物车路由上 cache-control: no-store 且 x-cache: Miss from cloudfront。
经验教训
1. WooCommerce 会话 Cookie 使用 wp_ 前缀。它是 wp_woocommerce_session_HASH,而不是 woocommerce_session。任何按前缀过滤 Cookie 的 CloudFront Function(或 WAF/CDN 规则)都必须显式包含 wp_woocommerce_session_。
2. 交易型路由必须按路径设为 no-store。/cart/、/checkout/ 和 /my-account/ 永远不应被缓存。按路径检测比按 Cookie 检测更稳健——它不依赖顾客是否已有会话。
3. 两个叠加的问题需要两处修复。即使在源站修好了 Cache-Control,只要 Function 移除会话 Cookie,WordPress 就会重建空会话。两层必须同时正确——只修一层会给人"修复无效"的错觉。
4. behavior 数量受限?从源站控制。当 CDN 套餐限制 behavior 数量时,出路不是为每个路由创建专用 behavior——而是通过源站的 Cache-Control 头来控制缓存,正确的缓存策略本就会遵从它。
CDN 后面的 Cookie 这个主题,之前在这里出现过——在一个 wp-login 上 auth_basic 引发的隐形 401 循环 中。区别在于:那里的问题是某个头被强制要求;这里则是某个 Cookie 被丢弃。
结论
当基础设施、CDN 和应用相互作用时,bug 很少只住在某一层。空购物车既不是孤立的"WooCommerce 的错",也不是"CloudFront 的错":它是一个沉默的源站(没有 Cache-Control)与一个过于尽职的 Function(丢弃了正确的 Cookie)之和。每一端的修复都很小——但它之所以奏效,正是因为两端被一起处理了。