# 埋点系统实施报告(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 例): 1. V2 迁移生效验证(`product_events` 表存在) 2. 匿名事件批次落库(202 accepted) 3. 未知事件名拒绝(202 rejected `unknown_event_name`) 4. eventId 幂等去重(第二次上传 202 duplicate) 5. props 字典外剥离(accepted,stripped 字段不入库) 6. 隐私红线字段拒绝(202 rejected `forbidden_field`) 7. 空批次参数校验(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 例): 1. trackEvent 带必需字段(不抛异常) 2. 隐私红线字段本地拒绝(silent drop) 3. identify 设置 userId 4. 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. 遗留项(按优先级) 1. **Flutter sessionId 生命周期**(M1): 引入 `session_tracker.dart`(WidgetsBindingObserver 监听前后台切换),冷启动或后台超 30 分钟重新生成,复用 `session.deviceId` 持久化逻辑。 2. **page_viewed 路由埋点**(M1): 集成 Flutter `RouteObserver`,自动在登录/注册/首页/个人中心页 `didPush` 时触发 `page_viewed`(pageName/referrer)。 3. **队列持久化**(M1 或 M2): 改用 `shared_preferences` 分段写入(按规范 §3.3),或评估引入 `sqflite`(报告 13 原建议)。当前内存队列 max 500 足够冷启动前积压,但进程杀死会丢失。 4. **auth_session_restore_* 事件**(M1): Splash 恢复流程完善后,在 `restoreSession()` 开始/成功/失败三处挂接。 5. **health_record_action**(M2): 档案增删改查实现后挂接。 6. **动态设备信息**(M1): 引入 `package_info_plus` / `device_info_plus` 读取真实 appVersion / osVersion。 7. **后端 GET /internal/events 查询端点**(M2 或审计需要时): 规范 §1 可选项,当前未实现(已有表和索引,补端点 1 小时)。 --- ## 4. 验收要点 ### 4.1 后端 - [x] Flyway V2 迁移生效(`platform.product_events` 表与三索引存在) - [x] `POST /api/v1/events` 匿名请求落库(202 accepted,无 token 不拒绝) - [x] 带 token 请求正常校验(无效 token 401/40101) - [x] eventId 去重(同 eventId 第二次上传 202 duplicate) - [x] 未知事件名拒绝(202 rejected `unknown_event_name`) - [x] props 字典外字段剥离(accepted,stripped 字段不入库) - [x] 隐私红线字段拒绝(202 rejected `forbidden_field`) - [x] 空批次 400 40000(参数校验) - [x] 门禁 82 测试全绿 ### 4.2 前端 - [x] 登录成功/失败挂接 `auth_login_succeeded` / `_failed` - [x] 注册成功/失败挂接 `auth_register_succeeded` / `_failed` - [x] 退出挂接 `auth_logout` - [x] 隐私红线本地校验(props key 命中模式不发送) - [x] identify / reset 生命周期正确 - [x] 门禁 34 测试全绿(4 个 analytics 新增测试) - ⚠️ page_viewed / auth_session_restore_* / health_record_action 留 TODO(M0 允许) --- ## 5. 后续接入指南 ### 5.1 新增事件类型 1. 后端 `EventDictionary` 加事件名与 props 白名单 2. 前端 `AnalyticsService.trackEvent()` 在业务点调用 3. 更新事件字典文档(报告 13 §4) 4. 两侧集成测试各补一例 ### 5.2 完整 sessionId 实现(M1) ```dart // 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 项(健康档案/动态设备信息/查询端点)。