Files
patbond-doc/docs/development/iterations/iteration-1/19-analytics-implementation-report.md
lixi 8e0e1c5c42 docs: 第一迭代收官——进展看板/功能清单更新 + 迭代总结
第一迭代已完成(2026-09-03 → 2026-09-04):
- 后端:82 测试(JWT 会话、compose 编排、埋点系统)
- 前端:34 测试(登录纵切、埋点模块)
- 真机联调 E2E 7/7 通过,契约偏差 0 个
- OpenAPI 契约正式化,ADR-001~008 落地
- 报告 18(E2E)、19(埋点)、20(迭代总结)入档
- 进展看板标注「第一迭代已完成」+ 交付总结
- 功能清单更新:compose 、联调 、测试数 82/34

验收状态:PASSED(对照审计 M1 要求)
下一步:M2 宠物健康档案;M1 完善项(sessionId 生命周期、page_viewed、CI 启用)

门禁:mkdocs build --strict 通过
2026-09-04 17:31:15 +08:00

9.7 KiB
Raw Permalink Blame History

埋点系统实施报告(M0 简化版)

角色: Senior Backend Developer + Senior Flutter Developer
日期: 2026-09-04
工单: 埋点系统落地(后端 + Flutter,第一迭代最后一块功能)
规范依据: 13-tracking-implementation-spec.md(事件定义、OpenAPI、DDL、隐私红线)

1. 交付成果

1.1 后端(patbond-api

提交: 6d47c5a — feat: 埋点接收端落地——V2 迁移 + POST /api/v1/events 批量上报(报告 13

核心组件:

  • V2__create_platform_product_events.sql: Flyway 迁移,platform.product_events 表(客户端 UUIDv7 主键即幂等键,user_id 不设外键,client_ts 合理性约束 ±30d/+1d,三索引按报告 13 §2.2
  • POST /api/v1/events: 批量上报端点(1-50 条、202 逐条结果 accepted/duplicate/rejected
  • EventDictionary: 事件字典 v111 个 auth_* 事件 + 工单增补 page_viewed/health_record_action),props 白名单,隐私红线模式(password|token|secret|phone|email|...
  • AnalyticsService/AnalyticsRepository/AnalyticsController: 事件处理管线(未知事件拒绝、字典外 props 剥离计数、红线字段整条拒绝、认证请求 userId 与 token subject 不一致拒绝)
  • BearerAuthFilter 可选鉴权: /api/v1/events 允许匿名(规范:唯一匿名写端点;带 token 仍严格验签 401/40101

测试数: 82 测试75 → 82),./mvnw clean test BUILD SUCCESS

新增测试(AnalyticsIntegrationTest 7 例):

  1. V2 迁移生效验证(product_events 表存在)
  2. 匿名事件批次落库(202 accepted
  3. 未知事件名拒绝(202 rejected unknown_event_name
  4. eventId 幂等去重(第二次上传 202 duplicate
  5. props 字典外剥离(acceptedstripped 字段不入库)
  6. 隐私红线字段拒绝(202 rejected forbidden_field
  7. 空批次参数校验(400 40000

日志红线遵守: props 内容不落日志(仅计数与字段名告警)。


1.2 前端(patbond-flutter

提交: 60d67a3 — feat: 埋点采集模块落地——AnalyticsService + 登录/注册/退出三事件(M0 简化版,报告 13)

核心组件:

  • lib/analytics/analytics_service.dart: AnalyticsServicetrackEvent(name, props?)/identify(userId)/reset()),隐私红线本地校验(props key 匹配 password|token|secret|phone|... 本地拒绝),匿名 ID 复用 session.deviceId,sessionId 简化为每事件生成(M0,完整实现需 session_tracker),网络失败静默丢弃(无重试,按规范)
  • props 白名单校验: 客户端不做(后端剥离,减少客户端与字典耦合)
  • 队列: 内存队列(max 500),满 20 触发上传;持久化到 shared_preferences 分段留 TODOM0 时间不够)

挂接点完成度 (报告 13 表 2 前端五事件,工单允许部分挂接):

  • 登录成功/失败: auth_login_succeededidentifierType/durationMs)、auth_login_failedfailureReason
  • 注册成功/失败: auth_register_succeededdurationMs)、auth_register_failedfailureReason
  • 退出: auth_logoutserverRevoked
  • 页面浏览: page_viewed(M0 无路由埋点基础,留 TODO 注释)
  • 会话恢复: auth_session_restore_*(Splash 恢复流程待完善,留 TODO)
  • 健康档案: health_record_action(M2 实现档案功能后挂接,留 TODO 注释)

测试数: 34 测试30 → 34),flutter test 全绿

新增测试(test/analytics/analytics_service_test.dart 4 例):

  1. trackEvent 带必需字段(不抛异常)
  2. 隐私红线字段本地拒绝(silent drop)
  3. identify 设置 userId
  4. reset 清除 userId 但保留 anonymousId

隐私红线遵守: props 携带 password|token|secret|phone|email|... key 模式本地拒绝,整条事件不发送。

Dart 格式化: 1 changedanalytics_service.dart),dart format 无错误

分析问题: flutter analyze 86 issues(与上一波同源,非本次引入)


2. 与规范的偏差(M0 简化策略)

规范要求 M0 实施 理由
sessionId 生命周期管理(冷启动/后台 30 分钟后重新生成) 每事件独立生成 UUID M0 无 WidgetsBindingObserver 集成,完整实现需 session_tracker.dart(留 TODO
队列持久化到 shared_preferences 分段 内存队列(max 500 M0 时间不够,sqflite 未引入、追加文件需 path_provider;内存队列足够冷启动前积压
page_viewed 四次挂接(登录/注册/首页/个人中心) 未实现 M0 无路由埋点基础(留 TODO 注释,M1 集成路由观察者后补齐)
auth_session_restore_* 三事件 未实现 Splash 恢复流程待完善(M0 仅占位,M1 实现后补齐)
health_record_action 未实现 M2 档案功能才有载体(留 TODO 注释)
appVersion / osVersion 动态读取 硬编码 1.0.0+1 / android-14 package_info_plus / device_info_plusM0 未引入(留 TODO

所有简化均为工单「时间不够可留 TODO」明确允许;核心管线(事件上报、字典校验、去重、隐私防护)完整交付。


3. 遗留项(按优先级)

  1. Flutter sessionId 生命周期M1: 引入 session_tracker.dartWidgetsBindingObserver 监听前后台切换),冷启动或后台超 30 分钟重新生成,复用 session.deviceId 持久化逻辑。
  2. page_viewed 路由埋点M1: 集成 Flutter RouteObserver,自动在登录/注册/首页/个人中心页 didPush 时触发 page_viewedpageName/referrer)。
  3. 队列持久化M1 或 M2: 改用 shared_preferences 分段写入(按规范 §3.3),或评估引入 sqflite(报告 13 原建议)。当前内存队列 max 500 足够冷启动前积压,但进程杀死会丢失。
  4. auth_session_restore_ 事件*M1): Splash 恢复流程完善后,在 restoreSession() 开始/成功/失败三处挂接。
  5. health_record_actionM2: 档案增删改查实现后挂接。
  6. 动态设备信息M1: 引入 package_info_plus / device_info_plus 读取真实 appVersion / osVersion。
  7. 后端 GET /internal/events 查询端点(M2 或审计需要时): 规范 §1 可选项,当前未实现(已有表和索引,补端点 1 小时)。

4. 验收要点

4.1 后端

  • Flyway V2 迁移生效(platform.product_events 表与三索引存在)
  • POST /api/v1/events 匿名请求落库(202 accepted,无 token 不拒绝)
  • 带 token 请求正常校验(无效 token 401/40101
  • eventId 去重(同 eventId 第二次上传 202 duplicate
  • 未知事件名拒绝(202 rejected unknown_event_name
  • props 字典外字段剥离(acceptedstripped 字段不入库)
  • 隐私红线字段拒绝(202 rejected forbidden_field
  • 空批次 400 40000(参数校验)
  • 门禁 82 测试全绿

4.2 前端

  • 登录成功/失败挂接 auth_login_succeeded / _failed
  • 注册成功/失败挂接 auth_register_succeeded / _failed
  • 退出挂接 auth_logout
  • 隐私红线本地校验(props key 命中模式不发送)
  • identify / reset 生命周期正确
  • 门禁 34 测试全绿(4 个 analytics 新增测试)
  • ⚠️ page_viewed / auth_session_restore_* / health_record_action 留 TODOM0 允许)

5. 后续接入指南

5.1 新增事件类型

  1. 后端 EventDictionary 加事件名与 props 白名单
  2. 前端 AnalyticsService.trackEvent() 在业务点调用
  3. 更新事件字典文档(报告 13 §4
  4. 两侧集成测试各补一例

5.2 完整 sessionId 实现(M1

// lib/analytics/session_tracker.dart
class SessionTracker with WidgetsBindingObserver {
  String _sessionId = const Uuid().v4();
  DateTime? _backgroundAt;
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      _backgroundAt = DateTime.now();
    } else if (state == AppLifecycleState.resumed) {
      if (_backgroundAt != null &&
          DateTime.now().difference(_backgroundAt!) > Duration(minutes: 30)) {
        _sessionId = const Uuid().v4();
      }
    }
  }
  
  String get sessionId => _sessionId;
}

AnalyticsService 构造时注入,替换当前的 Uuid().v4() 临时方案。


6. 数据质量验收清单(报告 13 §5)

指标 M0 状态 验收方式
去重命中率(重复 eventId 占比) 服务端 ON CONFLICT 生效 集成测试验证 duplicate 状态
隐私泄露零容忍(props 携带手机号/密码等) 前后端双重防护 测试覆盖 forbidden_field 拒绝路径
client_ts 合理性(±30d/+1d DDL 约束生效 数据库约束阻止异常插入
事件完整率(成功上报比例) ⚠️ M0 无持久化,进程杀死会丢 M1 队列持久化后达 95%+
sessionId 稳定性(同会话内不变) ⚠️ M0 每事件独立 UUID M1 SessionTracker 后达标

7. 总结

M0 交付状态: 埋点采集管线完整交付(事件上报、字典校验、去重、隐私防护),登录/注册/退出三核心事件挂接完成,82 后端测试 + 34 前端测试全绿。简化项(sessionId 生命周期、队列持久化、page_viewed 路由埋点)均为工单明确允许的 TODO,不影响核心功能验收。

测试数增量: 后端 75 → 82+7),前端 30 → 34+4

挂接点完成度: 3/5(登录/注册/退出完成,page_viewed / health_record_action 留 M1/M2

遗留项: 7 项,优先级明确,预计 M1 补齐前 4 项(sessionId/page_viewed/队列持久化/Splash 恢复事件),M2 补齐后 3 项(健康档案/动态设备信息/查询端点)。