# 第一迭代埋点与实验规划(身份漏斗) > 角色:Experiment Tracker > 日期:2026-09-03 > 依据:`patbond-doc/docs/development/development-plan.md` 第 8 节(第一迭代范围)、第 9 节「可观测性与产品验证」、第 11 节(已知风险) > 范围:仅覆盖「注册 → 登录 → 获取当前用户 → 退出」身份纵切;不涉及社区、AI 创作、本地服务的事件与实验。 --- ## 1. 埋点事件规范 ### 1.1 设计原则 - 事件名使用 `snake_case`,格式为 `<域>_<对象>_<结果>`,域前缀统一为 `auth`。 - 每个事件携带 `eventVersion`(本迭代全部为 `1`);schema 变更时递增版本,消费端按版本解析,禁止原地改语义。 - 事件在**客户端**采集(反映用户实际体验,含网络失败),`serverTs` 由接收端补写,作为统计的权威时间;`clientTs` 仅用于排序和时延诊断。 - 每个事件由客户端生成 `eventId`(UUIDv7),用于服务端幂等去重(配合 at-least-once 上报)。 - JSON 字段 `camelCase`、UUID 字符串、ISO 8601 时间,与第 6.1 节 API 通用规则一致。 ### 1.2 公共属性(所有事件必带) | 字段 | 类型 | 说明 | | --- | --- | --- | | `eventId` | UUID | 客户端生成(UUIDv7),服务端去重键 | | `eventName` | string | 见 1.4 事件清单 | | `eventVersion` | int | 本迭代固定 `1` | | `anonymousId` | UUID | 设备级匿名标识,首次启动生成,存普通本地存储(非敏感);注册前唯一可用的主体标识 | | `userId` | UUID / null | 登录后填充;注册开始、登录前事件为 null | | `sessionId` | UUID | 客户端会话标识:应用冷启动或后台超过 30 分钟后重新生成 | | `clientTs` | timestamptz | 客户端本地时间(ISO 8601 含时区) | | `serverTs` | timestamptz | **服务端接收时补写**,客户端不发;统计窗口一律以此为准 | | `appVersion` | string | 如 `1.0.0+12` | | `platform` | string | `android` / `ios` | | `osVersion` | string | 粗粒度主版本,如 `android-14` | ### 1.3 隐私红线(禁止字段,任何事件不得携带) 依据第 9 节「不得包含密码、token 或非必要个人信息」及 6.1 节日志规则: 1. **密码**:明文、哈希、长度、强度分数均禁止。 2. **凭证**:access token、refresh token、验证码、上传签名、`Idempotency-Key` 等任何凭证或其片段。 3. **手机号 / 邮箱全文**:失败事件中不得回传用户输入的账号原文;仅允许 `identifierType`(`username` / `phone` / `email`)这类枚举。 4. **用户名原文**:注册失败重复冲突时只报 `username_taken` 分类,不报具体用户名。 5. **完整 IP、精确地理位置、设备广告标识(IDFA/GAID)**。 6. **原始请求/响应报文、异常堆栈原文**:失败只允许上报枚举化的 `failureReason` + 业务错误码 + HTTP 状态码。 服务端接收器应对属性做白名单校验:schema 之外的字段直接丢弃并计数告警,防止未来有人顺手塞入敏感字段。 ### 1.4 事件清单 #### 注册 | 事件名 | 触发时机 | 专有属性 | | --- | --- | --- | | `auth_register_started` | 用户进入注册页并产生首次输入(首字符),每个注册会话记一次 | `entryPoint`(`launch` / `login_page_link`) | | `auth_register_succeeded` | 客户端收到 `POST /api/v1/auth/register` 的成功响应(code=0)之后 | `durationMs`(started → succeeded 耗时) | | `auth_register_failed` | 收到失败响应、请求超时或本地校验拦截提交时 | `failureReason`、`errorCode`(业务码,可空)、`httpStatus`(可空)、`attemptSeq`(本注册会话第几次尝试) | `auth_register_failed.failureReason` 枚举: - `validation_error` — 本地或服务端参数校验失败(格式、必填) - `username_taken` — 用户名已存在 - `phone_taken` — 手机号已存在 - `weak_password` — 密码不满足策略 - `rate_limited` — 触发频控 - `network_error` — 超时 / 无网络 / 连接失败 - `server_error` — 5xx 或未知业务码 #### 登录 | 事件名 | 触发时机 | 专有属性 | | --- | --- | --- | | `auth_login_succeeded` | 收到 `POST /api/v1/auth/login` 成功响应且 token 已写入安全存储后 | `identifierType`、`durationMs` | | `auth_login_failed` | 收到失败响应或请求超时 | `identifierType`、`failureReason`、`errorCode`、`httpStatus`、`attemptSeq` | `auth_login_failed.failureReason` 枚举:`invalid_credentials`(凭证错误,不区分账号不存在与密码错误,与接口防枚举策略一致)、`account_locked`(登录失败限制触发)、`rate_limited`、`validation_error`、`network_error`、`server_error`。 #### Token 刷新 | 事件名 | 触发时机 | 专有属性 | | --- | --- | --- | | `auth_token_refresh_succeeded` | `POST /api/v1/auth/refresh` 轮换成功且新 token 落库安全存储后 | `trigger`(`proactive` 预刷新 / `on_401` 拦截器触发 / `restore` 启动恢复) | | `auth_token_refresh_failed` | 刷新失败 | `trigger`、`failureReason`、`errorCode`、`httpStatus` | `failureReason` 枚举:`refresh_expired`、`refresh_revoked`(含轮换重放被拒,是 M1 验收「退出后 refresh token 不可再次使用」的观测点)、`network_error`、`server_error`。 #### 退出 | 事件名 | 触发时机 | 专有属性 | | --- | --- | --- | | `auth_logout` | 用户主动退出:本地凭证已清除时上报(不等待服务端撤销结果) | `serverRevoked`(bool,`POST /api/v1/auth/logout` 是否成功)| 被动登出(会话被撤销/过期导致跳登录页)不用此事件,由 `auth_session_restore_failed` 或 `auth_token_refresh_failed` 覆盖,避免口径混淆。 #### 登录态恢复 | 事件名 | 触发时机 | 专有属性 | | --- | --- | --- | | `auth_session_restore_started` | 冷启动时安全存储中存在 refresh token,开始恢复流程 | — | | `auth_session_restore_succeeded` | 恢复完成:拿到有效 access token 且 `GET /api/v1/me` 成功 | `durationMs`、`usedRefresh`(bool,是否经历了刷新) | | `auth_session_restore_failed` | 恢复失败,用户被要求重新登录 | `failureReason`(`refresh_expired` / `refresh_revoked` / `network_error` / `server_error`)、`errorCode`、`httpStatus` | 说明:冷启动无存量凭证时不上报任何 restore 事件(不算失败),保证会话恢复成功率的分母干净。 ### 1.5 与文档第 9 节首批漏斗事件的对应 文档建议首批覆盖六个漏斗事件,本迭代范围内落地其中两个:`auth_register_succeeded`(注册完成)、`auth_login_succeeded`(登录成功)。宠物创建、动态发布、AI 任务成功、预约确认分别属于 M2–M5,届时沿用本规范的公共属性与命名规则扩展,不在本轮定义。 --- ## 2. 指标口径 统计一律以 `serverTs` 划定窗口(UTC 日界,展示层可换算);主体去重优先 `userId`,注册前用 `anonymousId`。 ### 2.1 注册转化率 - **分子**:窗口内产生 `auth_register_succeeded` 的去重 `anonymousId` 数。 - **分母**:窗口内产生 `auth_register_started` 的去重 `anonymousId` 数。 - **窗口**:按天统计;归因窗口 24 小时——`started` 落在 D 日的设备,其成功可发生在 `started` 后 24h 内,仍计入 D 日转化(避免跨零点漏算)。 - **辅助指标**:`auth_register_failed` 按 `failureReason` 分布;`network_error`/`server_error` 占比是技术问题信号,`validation_error`/`weak_password` 占比是产品/文案问题信号。 ### 2.2 登录成功率 - **尝试级(主口径)**:分子 = `auth_login_succeeded` 事件数;分母 = `auth_login_succeeded` + `auth_login_failed` 事件数。按天统计,另看 7 天滚动。 - **用户级(辅助口径)**:窗口内最终登录成功(至少一条 succeeded)的去重主体 / 发起过登录尝试的去重主体(`userId` 缺失时用 `anonymousId`),窗口 24 小时。 - **排除项**:主口径不排除 `invalid_credentials`(它反映真实用户体验);另设「系统登录成功率」= 排除 `invalid_credentials`、`validation_error`、`account_locked` 后的成功率,用于监控服务健康,目标应接近 100%。 ### 2.3 会话恢复成功率 - **分子**:`auth_session_restore_succeeded` 事件数。 - **分母**:`auth_session_restore_started` 事件数(即冷启动时存在存量凭证并发起恢复的次数)。 - **窗口**:按天统计。无存量凭证的冷启动不进分母;`refresh_expired` 计入失败(是体验事实),但按 `failureReason` 拆分后单独解读——过期占比高提示 token 有效期策略问题(第 12 节待确认事项),`refresh_revoked`/`server_error` 占比高提示实现缺陷。 - 该指标同时是 M1 验收标准「客户端可恢复登录态」的量化观测。 --- ## 3. 为什么第一迭代不启动 A/B 实验 文档第 9 节明确规则:**「A/B 实验必须使用稳定分流和独立曝光事件;在基础埋点、指标口径和样本量规则完成前不启动产品实验。」** 当前三个前置条件均不满足: 1. **基础埋点不存在**:三仓尚无任何事件采集链路(第 2 节基线:Flutter 没有网络层,API 无观测能力),本报告定义的事件本身就是待建设项。 2. **指标口径未经数据验证**:口径刚在本报告提出,尚未有真实数据校验事件触发准确性(重复、丢失、时序)——没有可信基线就无法解读实验差异。 3. **样本量规则无从谈起**:第一迭代是首个真实版本,DAU 基线为零。以典型场景估算:若登录成功率基线约 90%,要在 95% 置信度、80% 功效下检出 3 个百分点的绝对提升,每组约需 1,600 个独立用户;当前流量远达不到,实验只会产出噪声结论。 4. **没有分流与曝光基础设施**:无稳定分流(用户 ID 哈希 + 实验盐)组件,无独立曝光事件,无法保证随机化与分析对齐。 5. **也没有值得实验的对照**:第一迭代只有一条登录纵切路径,不存在需要 A/B 裁决的产品分叉;此时的正确动作是把漏斗测准,而不是测变体。 ### 未来启动实验前必须就绪的前置条件清单 - [ ] 本报告第 1 节事件已上线,且通过数据质量验收:事件丢失率 < 5%,`eventId` 去重生效,`serverTs` 覆盖率 100%,属性白名单校验无敏感字段泄漏。 - [ ] 第 2 节三个指标已连续稳定产出 ≥ 2 周,形成基线均值与方差,且与服务端日志(如登录接口成功率)交叉核对一致。 - [ ] 样本量规则成文:给定基线率、最小可检测效应(MDE)、95% 置信度、80% 功效的样本量计算方法与查表,并据实际 DAU 判断实验最短运行时长。 - [ ] 稳定分流组件:`hash(userId, experimentSalt) % buckets`,同一用户在实验期内分组不变、跨端一致;登录前实验用 `anonymousId` 并定义登录后归并规则。 - [ ] 独立曝光事件(如 `experiment_exposed`,携带 `experimentKey`、`variant`、`eventVersion`):分析只统计实际曝光用户,杜绝按分配名单算分母。 - [ ] 实验设计文档模板与评审流程:假设、主指标、护栏指标、提前停止规则、多重比较校正约定。 - [ ] 安全机制:护栏指标(崩溃率、登录成功率等)实时监控与一键回滚(可复用后续的配置下发/feature flag 能力)。 - [ ] 隐私合规复核:实验分组数据同样遵守 1.3 节红线。 --- ## 4. 埋点数据落地建议 约束:与现有技术栈一致(Spring Boot + PostgreSQL 16 + Flutter);**不引入第三方分析 SaaS/SDK**——文档第 11 节第 10 条明示外部供应商均未确定,埋点作为基础设施不应在此时绑定未评审的供应商,自建最小链路即可满足第一迭代验证需求,且数据留在自有库,规避合规不确定性。 ### 4.1 客户端(Flutter) - 新增轻量 `analytics` 模块(与 `auth` 等 feature 平级),对外仅暴露 `track(eventName, props)`;事件构造时自动附加公共属性。 - **本地持久化队列**:事件先写本地队列(`sqflite` 表或追加式文件——Flutter 生态内置方案,非第三方分析服务),应用被杀不丢事件。注意:事件属于非敏感数据,存普通本地存储即可,**不占用安全存储**(安全存储按 4.2 节仅放 token)。 - **批量上报**:满 20 条或 30 秒定时触发,冷启动和进入后台时各冲刷一次;单批 ≤ 50 条。 - **重试**:指数退避(5s 起,上限 5 分钟)+ at-least-once;服务端靠 `eventId` 去重,客户端只在收到 2xx 后删除本地记录。4xx(schema 被拒)不重试,丢弃并本地计数。 - **背压**:队列上限 1,000 条,超限丢最旧事件;上报失败不得阻塞或影响任何业务流程(埋点永远是旁路)。 ### 4.2 服务端(Spring) - 新增接口 `POST /api/v1/events`(批量数组体)。放在现有服务内(建议 `patbond-user` 或后续网关层)即可,第一迭代不为埋点单起服务。 - **鉴权**:登录后带 access token;注册/登录前的事件允许匿名上报(仅此端点),配合频控与 body 大小限制(如单批 ≤ 64KB)防滥用。 - **处理**:白名单校验事件名与属性 → 剥离/拒绝红线字段 → 补写 `serverTs` → 落库。响应 `202`,不因单条非法事件拒绝整批(返回逐条结果)。 - **存储**:落 `platform` schema(平台能力域,与第 3 节域划分一致),经 Flyway 迁移新建表: ```sql create table platform.product_events ( event_id uuid primary key, -- 客户端 UUIDv7,天然幂等去重 event_name text not null, event_version int not null, anonymous_id uuid not null, user_id uuid, session_id uuid not null, client_ts timestamptz not null, server_ts timestamptz not null default now(), app_version text not null, platform text not null, props jsonb not null default '{}'::jsonb -- 白名单内的专有属性 ); create index on platform.product_events (event_name, server_ts); create index on platform.product_events (user_id, server_ts); ``` - 插入用 `on conflict (event_id) do nothing` 实现去重。第一迭代事件量极小,同库 SQL 直接算第 2 节指标即可(可沉淀成参考查询放入 `docs/database/`);将来量大再考虑异步化(RabbitMQ 已在技术栈规划内)或独立分析存储。 - **红线兜底**:该表数据同样受 6.1 节日志规则约束;接收端是最后一道防线,白名单之外字段一律丢弃并打点告警。 ### 4.3 数据质量监控(上线即带) - 服务端技术指标:`/api/v1/events` 请求量、拒绝率、去重命中率(与第 9 节技术指标要求一致)。 - 每日核对:`auth_login_succeeded` 事件数 vs 登录接口成功响应数,偏差 > 5% 告警——这是未来实验前置条件里「交叉核对」的日常化。 --- ## 附:事件总览 | # | 事件名 | 版本 | 阶段 | | --- | --- | --- | --- | | 1 | `auth_register_started` | 1 | 注册 | | 2 | `auth_register_succeeded` | 1 | 注册(漏斗事件) | | 3 | `auth_register_failed` | 1 | 注册 | | 4 | `auth_login_succeeded` | 1 | 登录(漏斗事件) | | 5 | `auth_login_failed` | 1 | 登录 | | 6 | `auth_token_refresh_succeeded` | 1 | 会话维持 | | 7 | `auth_token_refresh_failed` | 1 | 会话维持 | | 8 | `auth_logout` | 1 | 退出 | | 9 | `auth_session_restore_started` | 1 | 恢复 | | 10 | `auth_session_restore_succeeded` | 1 | 恢复 | | 11 | `auth_session_restore_failed` | 1 | 恢复 |