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

268 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 默认发送到生产。
```tsx
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`:未登录用户标识。
- `sessionId`30 分钟会话标识。
- `pagePath/pageTitle/referrer`
- `siteHost/pageUrl/UTM`
登录成功或恢复会话后设置 Token,让事件同时绑定 `userId`
```ts
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`
```tsx
<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`,否则浏览器无法可靠识别它是按钮。
不需要统计的区域:
```tsx
<div data-analytics-ignore>
{/* 此节点及其子节点不自动记录点击 */}
</div>
```
程序触发、没有真实 DOM 点击时可以手工记录:
```ts
window.CozsweetPaymentAnalytics?.elementClick(
'chat.retry_generation',
'Retry generation',
{ messageId },
)
```
不要把聊天正文、邮箱、手机号、Token 或支付参数放进 `data-analytics-key`、标签或 metadata。
## 5. 支付漏斗必须手工上报
通用按钮点击只能知道“点了某个按钮”,不能知道套餐价格、订单 ID 和进入充值页的原因。以下事件必须在支付业务代码对应步骤调用。
```ts
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,
)
}
```
轮询订单状态:
```ts
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 最终批量发送:
```http
POST /api/behavior/events
Content-Type: application/json
Authorization: Bearer <TOKEN> // 有登录 Token 时携带
```
```json
{
"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"
}
}
]
}
```
成功响应:
```json
{"ok": true, "inserted": 1}
```
埋点失败不能阻塞聊天、解锁或支付流程。SDK 已吞掉网络异常,业务代码不要 `await` 埋点后再继续支付。
## 8. 联调验收
1. 打开浏览器 Network,筛选 `behavior/events`
2. 首次打开页面应看到 `page_view`
3. Next.js 路由切换后应再次看到新 `pagePath``page_view`
4. 点击任意按钮应看到 `element_click` 和稳定 `elementKey`
5. 从付费墙点击一个套餐,依次检查专用支付事件及 `planId`
6. 建单成功后必须看到同一 `orderId` 出现在建单、支付页和状态事件中。
7. 接口响应必须为 `ok=true``inserted>0`
8. Manager 选择同一日期范围,确认数据出现;统计不是实时推送时,刷新页面即可。
## 9. 当前线上为 0 的原因
截至 2026-07-14,线上 `cozsweet.com` 页面及加载的前端 JS 中没有找到本 SDK,也没有找到支付漏斗事件代码。数据库近期大部分 `element_click/page_view` 来自 Manager 后台自己的追踪,而不是 Cozsweet 用户端。后端只能自动记录建单开始/成功/失败,无法替前端知道付费墙是否展示、套餐是否曝光、用户点击了哪个套餐、支付页是否真正打开,因此这些步骤显示 0 是符合当前数据事实的。