Skip to content

Product Analytics Events

Haisu Zuo edited this page Oct 9, 2026 · 1 revision

产品使用分析事件契约 v1

Reference. Implementation and current repository policy take precedence if this page differs.

  • 版本: 1.0
  • 状态: RND-409 Phase 0 契约;RND-162 实现、测试与评审的唯一事件定义来源
  • 适用范围: 平台超级管理员的跨租户产品使用分析
  • 不适用范围: 租户业务 Analytics、安全/合规审计、Session Replay、广告归因

1. 目标与边界

本契约只收集平台运营回答下列问题所需的最小事件集:

  1. 哪些已开通租户在指定周期内真实使用了产品;哪些租户连续未使用。
  2. 管理员登录是否成功,失败率是否异常。
  3. 会话查阅、搜索、筛选、媒体预览及设置等核心能力分别被多少租户和管理员采用。
  4. 单个租户最后一次登录及最后一次核心产品行为发生在何时。

产品使用分析与 AuditLog 是两个独立领域:审计日志用于安全、合规和业务操作证据,不能作为本契约的事件来源或存储替代品。产品事件也不得用于重放用户操作、查看聊天内容或改变任何业务授权决定。

第一版不建设第三方分析平台、事件存储/API、前端埋点或超管分析页面;这些由 RND-162 按本契约实现。

2. 术语与统计口径

名称 定义
已开通租户 tenants.lifecycle_status IN ('active', 'frozen', 'suspended') 的租户。provisioning 租户不计入。
核心产品行为 表 4 中标记为 core_workflow 的有效事件;登录成功、设置打开、语言变更和反馈提交不单独构成“真实使用”。
活跃租户 在查询窗口内至少发生一次核心产品行为的已开通租户。
活跃管理员 在查询窗口内至少发生一次核心产品行为的、可解析到内部 admin_user_id 的管理员。
最近登录 租户内最后一条 product.auth.login_succeeded.v1 的 occurred_at。
最近产品行为 租户内最后一条核心产品行为的 occurred_at;不把登录或失败登录冒充为产品使用。
未使用租户 已开通租户在连续未使用窗口内没有核心产品行为。它是运营跟进信号,不等同于已流失。
查询窗口 默认最近 30 个完整自然日;页面必须支持 7、14、30、60、90 日及明确起止时间。趋势按平台时区 Asia/Shanghai 的自然日聚合。

2.1 第一阶段指标

指标 业务含义 计算口径 来源
已开通租户数 当前可运营的租户基数 查询时符合“已开通租户”定义的租户数 tenants 当前状态
活跃租户数 / 活跃率 有真实核心使用的租户规模 窗口内活跃租户数;活跃率 = 活跃租户数 / 窗口结束时已开通租户数 全部 core_workflow 事件
未使用租户数 / 列表 需运营跟进的租户 连续 30 日(或用户指定窗口)无核心产品行为的已开通租户;列表显示最后产品行为时间 全部 core_workflow 事件 + tenants
最近登录时间 租户最近成功进入产品的时间 每租户 MAX(login_succeeded.occurred_at) product.auth.login_succeeded.v1
最近产品行为时间 租户最近真实使用产品的时间 每租户 MAX(core_workflow.occurred_at) 表 4 的核心事件
登录成功次数 / 失败次数 登录链路健康度 窗口内相应认证事件数;失败事件按已解析租户和未解析租户分别统计 两个登录事件
登录失败率 登录是否异常 失败次数 / (成功次数 + 失败次数);分母为 0 时显示“无登录尝试”,不显示 0% 两个登录事件
活跃管理员数 实际使用产品的管理员规模 窗口内 COUNT(DISTINCT admin_user_id),仅核心产品行为且 admin_user_id 非空 全部 core_workflow 事件
功能采用租户数 / 管理员数 各核心功能的采用情况 在窗口内至少发生一次该功能事件的去重 tenant / admin 数;同时展示事件量但不得只以事件量判断采用 表 4 的功能事件
登录与功能使用趋势 使用量是否变化 每自然日按事件名聚合的成功/失败登录、活跃租户、功能采用租户与事件量 所有 v1 事件

未解析租户的失败登录只进入平台总失败计数,不能归属到任一租户,也不得出现在任一租户详情中。

3. 统一事件模型

3.1 命名与版本策略

事件名的唯一格式为 product.<domain>.<action>.v<major>,全部使用小写 ASCII 与下划线。表 4 是 v1 的完整、封闭清单;实现不得临时新增同义事件名。

  • v1 后缀与 schema_version: 1 必须一致。
  • 可选字段的纯新增先更新本文档的次版本和测试;字段改名、删除、类型变化、语义变化或隐私等级放宽必须新建 v2 事件名。
  • 旧事件在其保留期内仍可查询;聚合层按事件名版本明确处理,不能把不同大版本静默混合。
  • 未列在本文档中的事件、字段或枚举值必须在采集边界拒绝,并记录不含事件载荷的内部技术计数。

3.2 统一信封

字段 类型 必填 隐私等级 规则
event_id UUID 是 P1 内部关联标识 由事件源为一次逻辑操作生成;重试必须复用同一值。
event_name string literal 是 P0 分类 必须是表 4 的完整事件名。
schema_version integer 是 P0 分类 v1 固定为 1。
occurred_at RFC 3339 UTC datetime 是 P0 时间 后端认证事件使用服务器时间;前端事件提交客户端时间,接收端校验后规范化,超出允许时钟漂移时使用接收时间。
received_at RFC 3339 UTC datetime 是 P0 时间 由接收端写入;不得由客户端提供。
tenant_id internal UUID/string 条件必填 P1 内部关联标识 从已认证服务端上下文派生,前端不得提交或覆盖。仅未解析的失败登录可为 null。
admin_user_id internal UUID/string 条件必填 P1 内部关联标识 从已认证服务端上下文派生;失败登录可为 null。绝不是企业微信 userid。
source enum 是 P0 分类 frontend 或 backend。
event_class enum 是 P0 分类 authentication、core_workflow、secondary_workflow。由事件名固定,不接受客户端任意值。

P0 是非敏感的固定分类/时间;P1 是受访问控制保护的内部或不可逆关联标识。所有事件字段都必须先经过表 4 的 allowlist 校验;未定义的键不可持久化。

3.3 采集、可靠性与隔离原则

RND-162 的实现必须遵守以下契约:

  1. 开关: PRODUCT_ANALYTICS_ENABLED 控制全部产品事件采集,默认 false;关闭时不创建事件、不排队、不重试。读取既有数据的超管分析权限不因开关关闭而扩大。
  2. 非阻塞: 前端仅以异步、best-effort 方式提交;后端核心业务路径不得等待统计持久化、重试或聚合完成。
  3. 失败降级: 采集、队列、存储、清理或统计接口故障必须被吞掉并产生安全的运维日志/指标;登录、会话查阅、搜索、筛选、媒体预览、设置与反馈的原有成功/失败语义均不得改变。
  4. 服务端赋权: 事件的 tenant_id、admin_user_id、事件名 allowlist 和服务端时间由接收端校验或派生;客户端提交的身份、租户、自由文本和任意扩展字段一律忽略/拒绝。
  5. 租户隔离: 普通租户路由不提供跨租户事件、聚合或明细。仅独立的 PlatformAdmin 身份可读取跨租户分析,且超管页面只返回本契约允许的聚合、时间和内部租户元数据,不返回事件敏感载荷。
  6. 无第三方出站: 第一版事件只写入本平台受控存储;不得把事件或其标识符发送给第三方分析、广告或会话重放服务。

4. v1 事件字典

所有下表事件均含 3.2 的统一信封。事件字段表只列额外字段;未列字段一律禁止。

事件名 类别 触发位置 / 成功条件 额外字段(类型、必填、隐私) 直接支持的指标
product.auth.login_succeeded.v1 authentication 后端:认证与会话建立成功后。仅一次,不因页面跳转重复。 无 最近登录、成功次数、失败率
product.auth.login_failed.v1 authentication 后端:认证尝试被拒绝后。不得记录账号输入值或拒绝原因原文。 tenant_resolution(enum resolved/unresolved,是,P0) 失败次数、失败率
product.conversation.review_opened.v1 core_workflow 前端:已认证管理员打开会话查阅入口并完成页面初始化。 无 活跃租户/管理员、会话查阅采用
product.directory.view_selected.v1 core_workflow 前端:管理员主动切换员工或联系人视图;初始默认视图不发送。 view_kind(enum employee/contact,是,P0) 员工/联系人视图采用
product.directory.subject_selected.v1 core_workflow 前端:管理员选择一个员工或联系人后,选择已被界面接受。 subject_kind(enum employee/contact,是,P0) 员工/联系人对象选择采用
product.conversation.detail_opened.v1 core_workflow 前端:打开一个具体会话并收到可渲染的成功结果后。 无 打开会话采用
product.conversation.older_messages_loaded.v1 core_workflow 前端:用户请求且成功加载更早消息后;“无更多消息”不是成功加载。 无 加载更早消息采用
product.search.executed.v1 core_workflow 前端:搜索请求已被提交并收到正常业务响应后,含零结果。 search_scope(enum contact_nickname/message_content/combined,是,P0) 搜索采用
product.search.filter_applied.v1 core_workflow 前端:用户应用或清除一个筛选维度并收到正常业务响应后;每个维度单独一条。 filter_dimension(enum date/person/message_type,是,P0);filter_action(enum applied/cleared,是,P0) 筛选器采用与维度采用
product.media.preview_opened.v1 core_workflow 前端:媒体预览容器成功打开后;下载失败或仅显示占位符不发送。 无 媒体预览采用
product.settings.opened.v1 secondary_workflow 前端:管理员打开设置页并完成初始化后。 无 设置采用(不计入真实使用)
product.settings.language_changed.v1 secondary_workflow 后端:语言偏好成功保存后。 无 语言设置采用(不计入真实使用)
product.feedback.submitted.v1 secondary_workflow 后端:反馈入口存在且反馈已成功持久化后。当前反馈入口由 RND-161 提供;若该能力被关闭,本事件不发送,不阻塞其他 v1 事件。 无 反馈入口采用(不计入真实使用)

4.1 事件字段的必要性说明

  • 员工/联系人选择不记录被选择人的名字、备注、企业微信 userid、数据库主键或稳定哈希;本阶段只需判断该能力是否被采用及选择的是哪类对象。
  • 会话打开不记录会话 ID、群 ID、成员、标题或任何消息。
  • 搜索和筛选只记录固定的功能类别;不记录关键词、日期范围、人员、消息类型具体值或搜索结果。
  • 媒体预览、设置、语言变更和反馈提交不记录任何额外字段;不记录媒体 ID、URL、文件名、MIME、大小、内容、语言值、反馈正文或反馈附件。
  • 新增任何枚举或字段前必须更新本文档并完成隐私评审;不得以自由文本、路径、页面标题或 query string 充当事件字段。

5. 指标到事件映射

运营问题 使用的数据 / 事件 不可替代的原因
哪些租户近期真实使用? 已开通租户状态 + 任一 core_workflow 事件 登录或安全审计不能证明产品核心功能被实际使用。
哪些租户连续未使用? 已开通租户状态 + 每租户核心事件的最大时间 可显示最后产品行为,且不把停用或配置中的租户误判为流失。
管理员是否能成功登录、失败是否异常? 两个 product.auth.* 事件 后端在认证结果已确定时记录,避免依赖前端页面加载。
哪些管理员在活跃使用? core_workflow 的内部 admin_user_id 去重 以真正的产品动作而非安全审计或企业微信身份判断。
会话查阅是否采用? review_opened、view_selected、subject_selected、detail_opened、older_messages_loaded 分别回答入口、视图切换、对象选择、会话打开和历史浏览。
搜索与筛选是否采用? search.executed、filter_applied 可按范围、结果状态和筛选维度分析,且不读取查询/筛选原文。
媒体预览、设置、语言、反馈是否采用? 对应 media、settings、feedback 事件 只收集功能发生与必要枚举,不收集业务内容或反馈正文。

6. 去重与幂等

  1. 每次逻辑用户操作只生成一个 UUID event_id。网络重试、前端重新发送和队列重放必须携带原 event_id。
  2. RND-162 事件存储对 event_id 建唯一约束;重复值视为成功但不新增数据。认证事件同样使用每次认证请求生成的单一 event_id。
  3. 前端不得因组件重渲染、轮询、自动刷新或失败重试生成新的事件。页面初始化类事件仅在该页面实例首次完成初始化时生成一次。
  4. 不以“短时间窗口”粗暴合并不同的用户动作,避免将真实的连续搜索、筛选或会话打开静默丢弃。幂等保障针对重复投递,不改变用户行为语义。
  5. 去重键、队列元数据和运维日志不得包含 Cookie、Token、请求 body、搜索词、对象 ID 或媒体标识。

7. 保留与清理

数据 保留期 清理策略
原始产品事件 180 天 每日异步分批删除 occurred_at 早于 180 天的记录;失败仅告警/重试,绝不阻塞主业务。
按天、租户、事件名聚合的统计行 24 个月 每月异步删除早于 24 个月的聚合行。
事件幂等键 与原始产品事件相同的 180 天 event_id 是原始事件主键,无单独去重表;原始事件删除时一并移除。

保留期从事件被持久化时执行。部署调度每日运行 backend/scripts/purge_product_analytics_events_once.py;它仅调用产品事件清理逻辑。清理只处理 RND-162 产品分析存储,不删除或修改 AuditLog、归档消息、媒体、登录会话或其他业务数据。任何保留期变更均须更新本文档版本、迁移/作业测试和运维说明。

8. 明确禁止采集

任一产品分析事件、请求日志、错误日志、死信队列、去重记录或聚合结果均不得包含:

  • 聊天正文、消息片段、解密内容、会话标题、群成员或收发对象;
  • 搜索关键词原文、搜索结果、联系人昵称、备注名、企业微信 userid;
  • 筛选值原文(包括具体日期、人员、消息类型、会话 ID);
  • 媒体 URL、路径、文件名、媒体 ID、MIME 原文、文件内容或缩略图;
  • Cookie、Session ID、Token、Secret、密码、授权码、HTTP Authorization、请求/响应完整 body;
  • IP 地址、User-Agent、指纹、完整页面 URL/query string、完整 DOM、截图、Session Replay 或热力图数据;
  • 反馈正文、附件、联系信息或 AI 对话内容。

允许的关联标识只有服务端派生的内部 tenant_id 与 admin_user_id,并只在受限的产品分析存储和超管查询中使用;它们不得暴露给普通租户管理员或第三方。

9. RND-162 实现与 QA 闸门

RND-162 开发前必须以本契约建立事件 allowlist、配置开关、存储、清理作业、聚合查询和超管只读页面。至少应自动化验证:

  1. PRODUCT_ANALYTICS_ENABLED=false 时所有事件源均不写入、不排队,原业务结果不变。
  2. 采集和存储异常被安全降级,登录、会话、搜索、筛选、媒体、设置和反馈的业务流程不受影响。
  3. 普通租户管理员不能读取跨租户事件、聚合或任一其他租户的数据;只有 PlatformAdmin 可读超管分析接口。
  4. 重复 event_id 只产生一条事件/聚合贡献;重试不污染趋势和采用率。
  5. 每一个表 4 事件仅接收声明字段,敏感字段或未知字段被拒绝且不进入日志。
  6. 超管 API 和页面只显示本文定义的统计、时间、租户元数据与内部受限标识,绝不返回第 8 节禁止字段。
  7. 原始事件、聚合和去重记录的保留/清理边界按第 7 节可重复验证。

任何新增产品问题、指标或事件都应先更新本契约并完成隐私评审,再改变采集实现。

Clone this wiki locally