Files
cozsweet-frontend-nextjs/docs/backend/FRONTEND_COZSWEET_BEHAVIOR_ANALYTICS.md
T

8.1 KiB
Raw Blame History

Cozsweet 全站行为埋点前端接入

发布状态(2026-07-14):自动页面/按钮采集 SDK 已部署 pro 预发,生产仍是旧版支付专项 SDK。前端应先接 pro 完成联调;生产自动采集要等 unified 下一次生产镜像发布后再启用。

1. 目标

前端接入一次后,自动统计:

  • 用户访问了哪个页面。
  • Next.js 站内路由切换。
  • 所有原生按钮、链接和带 role="button" 的控件点击。
  • data-analytics-key 的自定义可点击组件。

支付漏斗另外上报明确业务事件,统计付费墙原因、套餐曝光、套餐点击、创建订单、打开支付页和最终支付结果。

2. 环境地址

环境 SDK 事件接口
pro 预发 https://proapi.banlv-ai.com/js/cozsweet-payment-analytics.js https://proapi.banlv-ai.com/api/behavior/events
production 生产 https://api.banlv-ai.com/js/cozsweet-payment-analytics.js https://api.banlv-ai.com/api/behavior/events

3. Next.js 根布局接入

只在根布局加载一次。预发必须先指定预发事件地址,否则 SDK 默认发送到生产。

import Script from 'next/script'

export function BehaviorAnalyticsScripts() {
  const isPro = process.env.NEXT_PUBLIC_APP_ENV === 'pro'
  const apiBase = isPro
    ? 'https://proapi.banlv-ai.com'
    : 'https://api.banlv-ai.com'

  return (
    <>
      <Script id="cozsweet-analytics-config" strategy="beforeInteractive">
        {`window.COZSWEET_ANALYTICS_ENDPOINT=${JSON.stringify(
          `${apiBase}/api/behavior/events`,
        )}`}
      </Script>
      <Script
        src={`${apiBase}/js/cozsweet-payment-analytics.js`}
        strategy="afterInteractive"
      />
    </>
  )
}

SDK 会自动生成并持久化:

  • anonymousId:未登录用户标识。
  • sessionId30 分钟会话标识。
  • pagePath/pageTitle/referrer
  • siteHost/pageUrl/UTM

登录成功或恢复会话后设置 Token,让事件同时绑定 userId

window.CozsweetPaymentAnalytics?.setToken(accessToken)

4. 所有按钮点击

以下元素会自动上报 eventType=element_click,前端不需要在每个 onClick 里手工请求接口:

  • button
  • a[href]
  • [role="button"]
  • [role="link"]
  • input[type="button|submit|reset"]
  • [data-analytics-key]

重要按钮必须提供稳定且不随语言变化的 data-analytics-key

<button data-analytics-key="chat.send_message">Send</button>

<button data-analytics-key="recharge.open" data-analytics-label="Open recharge">
  Recharge
</button>

<div
  role="button"
  data-analytics-key="private_album.unlock"
  data-analytics-label="Unlock private album"
  onClick={unlockAlbum}
/>

div onClick 如果没有 role="button",必须添加 data-analytics-key,否则浏览器无法可靠识别它是按钮。

不需要统计的区域:

<div data-analytics-ignore>
  {/* 此节点及其子节点不自动记录点击 */}
</div>

程序触发、没有真实 DOM 点击时可以手工记录:

window.CozsweetPaymentAnalytics?.elementClick(
  'chat.retry_generation',
  'Retry generation',
  { messageId },
)

不要把聊天正文、邮箱、手机号、Token 或支付参数放进 data-analytics-key、标签或 metadata。

5. 支付漏斗必须手工上报

通用按钮点击只能知道“点了某个按钮”,不能知道套餐价格、订单 ID 和进入充值页的原因。以下事件必须在支付业务代码对应步骤调用。

const analytics = window.CozsweetPaymentAnalytics

analytics?.paywallShown('daily_chat_limit', {
  entryPoint: 'chat_input',
  isVip: false,
})

analytics?.rechargeModalOpen('chat_input', 'daily_chat_limit')

plans.forEach((plan, index) => {
  analytics?.planImpression(plan, index + 1)
})

analytics?.planClick(plan, {
  entryPoint: 'paywall_modal',
  triggerReason: 'daily_chat_limit',
})

analytics?.createOrderStart(plan, payChannel)

try {
  const response = await createOrder({
    planId: plan.planId,
    payChannel,
    autoRenew,
  })
  const data = response.data
  const orderId = data.orderId
  const checkoutUrl = analytics?.extractCheckoutUrl(data)

  analytics?.createOrderSuccess(plan, orderId, payChannel)
  if (checkoutUrl) {
    analytics?.checkoutOpened(orderId, plan, payChannel, checkoutUrl)
    window.location.href = checkoutUrl
  } else {
    analytics?.checkoutFailed(
      orderId,
      plan,
      'missing_checkout_url',
      payChannel,
    )
  }
} catch (error) {
  analytics?.createOrderFailed(
    plan,
    error instanceof Error ? error.message : 'create_order_error',
    payChannel,
  )
}

轮询订单状态:

analytics?.statusPollStart(orderId, plan)

analytics?.statusPollUpdate(orderId, data.status, plan, {
  expiresAt: data.expiresAt,
  expiresInSeconds: data.expiresInSeconds,
})

// 前端主动放弃轮询且订单仍未进入终态时调用
analytics?.statusPollTimeout(orderId, plan)

6. 固定事件名

步骤 事件名 必要 metadata
触发付费墙 paywall_shown triggerReason, entryPoint
打开充值页 recharge_modal_open triggerReason, entryPoint
套餐曝光 payment_plan_impression planId, amountCents, currency, position
点击套餐 payment_plan_click planId, amountCents, currency
开始建单 payment_create_order_start planId, payChannel
建单成功 payment_create_order_success planId, orderId, payChannel
建单失败 payment_create_order_failed planId, reason, payChannel
打开支付页 payment_checkout_opened planId, orderId, payChannel
支付页缺失/打开失败 payment_checkout_failed orderId, reason
支付状态 payment_status_paid/failed/expired orderId, status
轮询超时 payment_status_poll_timeout orderId

triggerReason 使用固定值:

  • daily_chat_limit
  • private_topic_limit
  • insufficient_credits
  • sidebar_recharge
  • vip_cta
  • ad_landing
  • manual_recharge
  • unknown

不要继续使用已经废弃的 weekly_chat_limit 作为新事件原因;当前免费聊天口径是每日额度。

7. 请求格式

SDK 最终批量发送:

POST /api/behavior/events
Content-Type: application/json
Authorization: Bearer <TOKEN>  // 有登录 Token 时携带
{
  "events": [
    {
      "eventType": "element_click",
      "eventName": "element_click",
      "anonymousId": "browser-uuid",
      "sessionId": "session-uuid",
      "pagePath": "/chat",
      "pageTitle": "Chat",
      "elementKey": "recharge.open",
      "elementText": "Recharge",
      "elementTag": "button",
      "metadata": {
        "siteHost": "cozsweet.com",
        "pageUrl": "https://cozsweet.com/chat"
      }
    }
  ]
}

成功响应:

{"ok": true, "inserted": 1}

埋点失败不能阻塞聊天、解锁或支付流程。SDK 已吞掉网络异常,业务代码不要 await 埋点后再继续支付。

8. 联调验收

  1. 打开浏览器 Network,筛选 behavior/events
  2. 首次打开页面应看到 page_view
  3. Next.js 路由切换后应再次看到新 pagePathpage_view
  4. 点击任意按钮应看到 element_click 和稳定 elementKey
  5. 从付费墙点击一个套餐,依次检查专用支付事件及 planId
  6. 建单成功后必须看到同一 orderId 出现在建单、支付页和状态事件中。
  7. 接口响应必须为 ok=trueinserted>0
  8. Manager 选择同一日期范围,确认数据出现;统计不是实时推送时,刷新页面即可。

9. 当前线上为 0 的原因

截至 2026-07-14,线上 cozsweet.com 页面及加载的前端 JS 中没有找到本 SDK,也没有找到支付漏斗事件代码。数据库近期大部分 element_click/page_view 来自 Manager 后台自己的追踪,而不是 Cozsweet 用户端。后端只能自动记录建单开始/成功/失败,无法替前端知道付费墙是否展示、套餐是否曝光、用户点击了哪个套餐、支付页是否真正打开,因此这些步骤显示 0 是符合当前数据事实的。