- 09 V5+community 骨架(191→206)、10 埋点队列三项(272→286)、 11 契约草案(13 路径/19 操作)、12 防泄漏三仓落地、 13 media MinIO 闭环 + auth 契约测试(→226,抓修 1 漂移)、14 收口总表 - backend-modules.md 更新五模块/六容器口径 - 媒体凭据形态已定型(契约冻结输入),剩余待定型点在 T3-04/05 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# 埋点队列三项完善实施报告(M3 第一波 T3-19 前端半边)
|
||||
|
||||
> 作者:Frontend Developer(Flutter)
|
||||
> 日期:2026-09-08
|
||||
> 依据:`iteration-2/15-analytics-persistent-queue.md` §4 遗留清单、`iteration-3/06-experiment-tracking-plan.md` §2.3(三项处置与优先级)、`iteration-1/13-tracking-implementation-spec.md` §3.3/§3.4
|
||||
> 仓库:patbond-flutter dev 分支,提交 `4d40c38`(基线 `720865b`)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
15 号报告 §4 留下队列三个未做项:30 秒定时冲刷、失败退避、anonymousId 持久化。iteration-3 06 号 §2.3 把三项全部排入 M3(定时冲刷 P1:社区长前台会话最多积压 19 条不上传;anonymousId P2:A/B 前置 #4「登录前分流」硬依赖),ADR-020 拍板升为第一波必做。本波三项一次落地,既有语义(flushNow、4xx 毒丸丢弃、at-least-once 删段、按段拼批 ≤50、eventId UUIDv7)零回退。
|
||||
|
||||
## 2. 设计要点
|
||||
|
||||
### 2.1 30 秒定时冲刷(13 号 §3.4 第 4 触发点,四触发点补齐)
|
||||
|
||||
- `AnalyticsService` 新增 `startPeriodicFlush()` / `stopPeriodicFlush()`:前台期间 `Timer.periodic`(周期 `flushInterval`,默认 30 秒,构造参数化便于测试)触发 `_flush()`;`startPeriodicFlush` 幂等(`??=`),不叠加定时器。
|
||||
- 生命周期挂接(`app.dart` + `SessionTracker`):
|
||||
- `SessionTracker` 新增 `onEnterForeground` 回调,复用既有「只在离开/回到 resumed 的第一次变更触发」的级联去重逻辑——回前台级联 `hidden → inactive → resumed` 只回调一次,冷启动首个 resumed(此前未离开过前台)不触发。
|
||||
- App `initState` 启动定时器;退后台回调改为「停定时器 + `flushNow()`」(既有退后台冲刷保留);回前台恢复定时器;App `dispose` 停定时器(widget 测试无悬挂 Timer)。
|
||||
- 与既有触发共存:满 20 条、退后台 `flushNow`、冷启动 `restore` 三个触发点原样保留;队列为空时定时器 tick 是廉价空转(`takeBatch` 为空即返回,无网络请求、无持久化写)。
|
||||
|
||||
### 2.2 失败指数退避(06 号 §2.3:客户端退避先行,不依赖后端限流)
|
||||
|
||||
- 上传失败(网络错误/5xx)后进入退避:首次 30s,×2 递增(30s→60s→120s→240s),封顶 5 分钟;退避窗口内**定时冲刷 tick 直接跳过**,到点后下一 tick 重试。
|
||||
- 任一批上传拿到服务端应答(202 受理或 4xx 拒绝——连通性已恢复)即重置退避,恢复 30 秒节奏。
|
||||
- **退避只挡定时冲刷**:`flushNow`(退后台)、满 20 条、冷启动 `restore` 等显式触发不受限——退后台是最后的上传窗口,不能被退避挡掉。
|
||||
- **429 处理**:从「4xx 毒丸丢弃」改为按网络错误同路径(保段 + 退避重试)。后端限流从未实现(iteration-2/09 出入项核实),`Retry-After` 精细分支待其落地后一并做,代码内已留注释说明。
|
||||
- 时钟经构造注入(`now` 参数,照 SessionTracker 先例),退避判定测试免真实等待。
|
||||
|
||||
### 2.3 anonymousId 持久化(13 号 §3.3 key,跨启动稳定)
|
||||
|
||||
- 现状是每次冷启动 `Uuid().v4()` 随机生成,登录前事件无法跨启动归并。本波在 `restore()` 中增加采用/落盘:shared_preferences key `pb.analytics.anonymousId` 已有值则采用;无值则把本次构造生成的 v4 落盘——首次生成后跨冷启动稳定。
|
||||
- 读取/写入失败(持久化不可用)降级为进程内临时 id,只打日志绝不抛出(埋点旁路原则),埋点照常入队。
|
||||
- 构造显式注入 `anonymousId` 的测试通道不参与持久化采用/落盘,既有测试语义(`anon-123` 断言)不受影响。
|
||||
- 已知边界:`restore()` 完成前 track 的事件仍带构造时的临时 id(首启时两者同值无影响;后续启动 app 装配层在挂接 tracker 前即调用 restore,实际窗口趋近于零),记录备查。
|
||||
|
||||
### 2.4 可测性改造
|
||||
|
||||
`_upload` 提升为 `@protected @visibleForTesting` 的 `uploadBatch`:定时/退避测试以假上传子类替换网络层,在 `fakeAsync` 内驱动 `Timer.periodic`(真实 HttpServer 在假异步区无法完成 IO)。既有 HttpServer 集成测试不受影响,继续走真实 HTTP 路径。
|
||||
|
||||
## 3. 测试变化
|
||||
|
||||
- 基线 272 → **286 全绿**(+14);`flutter analyze` 0 问题、`dart format` 无 diff。
|
||||
- 新增 `test/analytics/analytics_flush_scheduler_test.dart`(8 个,fakeAsync + 时钟注入 + 假上传子类):29 秒不触发 / 30 秒冲刷不满额队列、空队列不发起上传、stop 停 start 恢复(退后台/回前台)、start 幂等不叠加、30s→60s→120s 退避序列且窗口内 tick 跳过、退避封顶 5 分钟(240s×2 → 300s)、退避期间 flushNow 不受限、成功重置退避恢复 30 秒节奏。
|
||||
- `analytics_persistent_queue_test.dart` +4:429 保段不丢弃不计丢弃数;anonymousId 首次 restore 落盘、冷启动新实例沿用存储值且事件携带、构造注入通道不被覆盖。
|
||||
- `analytics_service_test.dart` +1:持久化不可用时 restore 降级临时 id 不崩溃。
|
||||
- `session_tracker_test.dart` +1:前后台回调级联下成对各触发一次,重复 resumed 不触发。
|
||||
- 既有语义回归零改动:4xx 毒丸、at-least-once、40+20 分批、flushNow 等原测试全部原样通过。
|
||||
- 依赖:dev_dependencies 显式声明 `fake_async ^1.3.3`(flutter_test 既有传递依赖,无新增第三方)。
|
||||
|
||||
## 4. 与 15 号 §4 遗留清单对照
|
||||
|
||||
| 15 号 §4 未做项 | 本波状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 30 秒定时冲刷 | **已做** | 前台 Timer.periodic,退后台停/回前台恢复;四触发点补齐 |
|
||||
| 指数退避 | **已做** | 30s ×2 封顶 5min,只挡定时冲刷,成功即重置;15 号原案「5s ×2」按 T3-19 拍板参数调整为 30s 起步 |
|
||||
| 429 按 Retry-After | **部分**(范围内的全部) | 429 已从毒丸丢弃改为保段退避;Retry-After 精细分支依赖后端限流(09 号出入项,未实现),随其落地一并做 |
|
||||
| `pb.analytics.anonymousId` 持久化 | **已做** | 首次生成落盘、跨启动稳定、失败降级临时 id |
|
||||
| `lastActiveAt` 持久化 | 不做(维持决策) | 03 号评估 §3.1 已裁定会话纯内存方案,非遗留项 |
|
||||
| 401 去 Authorization 重试一次 | 未做 | 不在 T3-19 三项范围,继续遗留 |
|
||||
|
||||
## 5. 交付物
|
||||
|
||||
- 代码:patbond-flutter `dev` 提交 `4d40c38`(已推送),改动 9 文件 +445/−14。
|
||||
- 新增:`test/analytics/analytics_flush_scheduler_test.dart`
|
||||
- 修改:`lib/analytics/analytics_service.dart`(定时器、退避、anonymousId 持久化、uploadBatch 可测性)、`lib/analytics/session_tracker.dart`(onEnterForeground)、`lib/app/app.dart`(定时器生命周期装配)、`pubspec.yaml`/`pubspec.lock`(fake_async 显式声明)、3 个既有测试文件
|
||||
Reference in New Issue
Block a user