Buzeli
buzeliSoluções Digitais
WordPress

自动清空的购物车:CloudFront 的 Cookie 前缀如何让 WooCommerce 销售归零

发布于 2026年6月18日

Ilustração de um carrinho de compras vazio e um cookie quebrado em uma rede de borda CDN

症状:弹窗购物车满的,购物车页面却是空的

一个 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 reload

CloudFront 失效——先对受影响的路由做一次清除,这样就不必等待旧的 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)之和。每一端的修复都很小——但它之所以奏效,正是因为两端被一起处理了。