第一迭代已完成(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 通过
9.7 KiB
埋点系统实施报告(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: 事件字典 v1(11 个 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 例):
- V2 迁移生效验证(
product_events表存在) - 匿名事件批次落库(202 accepted)
- 未知事件名拒绝(202 rejected
unknown_event_name) - eventId 幂等去重(第二次上传 202 duplicate)
- props 字典外剥离(accepted,stripped 字段不入库)
- 隐私红线字段拒绝(202 rejected
forbidden_field) - 空批次参数校验(400 40000)
日志红线遵守: props 内容不落日志(仅计数与字段名告警)。
1.2 前端(patbond-flutter)
提交: 60d67a3 — feat: 埋点采集模块落地——AnalyticsService + 登录/注册/退出三事件(M0 简化版,报告 13)
核心组件:
lib/analytics/analytics_service.dart:AnalyticsService(trackEvent(name, props?)/identify(userId)/reset()),隐私红线本地校验(props key 匹配password|token|secret|phone|...本地拒绝),匿名 ID 复用session.deviceId,sessionId 简化为每事件生成(M0,完整实现需session_tracker),网络失败静默丢弃(无重试,按规范)- props 白名单校验: 客户端不做(后端剥离,减少客户端与字典耦合)
- 队列: 内存队列(max 500),满 20 触发上传;持久化到
shared_preferences分段留 TODO(M0 时间不够)
挂接点完成度 (报告 13 表 2 前端五事件,工单允许部分挂接):
- ✅ 登录成功/失败:
auth_login_succeeded(identifierType/durationMs)、auth_login_failed(failureReason) - ✅ 注册成功/失败:
auth_register_succeeded(durationMs)、auth_register_failed(failureReason) - ✅ 退出:
auth_logout(serverRevoked) - ⬜ 页面浏览:
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 例):
- trackEvent 带必需字段(不抛异常)
- 隐私红线字段本地拒绝(silent drop)
- identify 设置 userId
- reset 清除 userId 但保留 anonymousId
隐私红线遵守: props 携带 password|token|secret|phone|email|... key 模式本地拒绝,整条事件不发送。
Dart 格式化: 1 changed(analytics_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_plus,M0 未引入(留 TODO) |
所有简化均为工单「时间不够可留 TODO」明确允许;核心管线(事件上报、字典校验、去重、隐私防护)完整交付。
3. 遗留项(按优先级)
- Flutter sessionId 生命周期(M1): 引入
session_tracker.dart(WidgetsBindingObserver 监听前后台切换),冷启动或后台超 30 分钟重新生成,复用session.deviceId持久化逻辑。 - page_viewed 路由埋点(M1): 集成 Flutter
RouteObserver,自动在登录/注册/首页/个人中心页didPush时触发page_viewed(pageName/referrer)。 - 队列持久化(M1 或 M2): 改用
shared_preferences分段写入(按规范 §3.3),或评估引入sqflite(报告 13 原建议)。当前内存队列 max 500 足够冷启动前积压,但进程杀死会丢失。 - auth_session_restore_ 事件*(M1): Splash 恢复流程完善后,在
restoreSession()开始/成功/失败三处挂接。 - health_record_action(M2): 档案增删改查实现后挂接。
- 动态设备信息(M1): 引入
package_info_plus/device_info_plus读取真实 appVersion / osVersion。 - 后端 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 字典外字段剥离(accepted,stripped 字段不入库)
- 隐私红线字段拒绝(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 留 TODO(M0 允许)
5. 后续接入指南
5.1 新增事件类型
- 后端
EventDictionary加事件名与 props 白名单 - 前端
AnalyticsService.trackEvent()在业务点调用 - 更新事件字典文档(报告 13 §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 项(健康档案/动态设备信息/查询端点)。