Files
patbond-doc/docs/development/device-verification.md
T
lixi f5457c2f4c
CI / docs-build (push) Successful in 2m2s
docs: M3 第三波收口——报告 21~27 入档挂导航
- 21~26 Flutter 社区接入五单 + 字典 v3 白名单(flutter 286→502、api 325→334)
- 27 收口总表:社区 demo 三页消亡、M3 四条验收标准逐条取证、
  乐观更新与媒体链路端到端、实现期修正记录
- device-verification.md 的 M3 四项真机步骤已由各单收口补全

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 14:48:33 +08:00

169 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 真机验证清单(常设)
> **定位**:跨迭代常设文档——凡「只能在真机/模拟器上验证」的事项都登记在此,按迭代分节;每项含操作步骤、通过标准与执行记录。真机到位或发版前照单执行。
> **维护约定**:各迭代收官时把真机专属验证项登记进来;完成后填执行记录并同步 [功能完成清单](feature-checklist.md) 对应条目状态。
> 原位置为 iteration-2/30 号报告,2026-09-08 提升为常设文档(M3 起亦有真机项)。
## 通用前置准备
**设备**Android 真机(推荐)或 Android 模拟器。桌面/Web 不可用——没有真实的移动端后台生命周期(`paused` 不触发),且 platform 值不在契约枚举内会被服务端整批拒绝。
**后端**(工作机上,进入你本地检出的 patbond-api 仓库目录执行):
```bash
cd <你的工作区>/patbond-api
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package
docker compose up -d --build
docker compose ps # 全部容器 Uppostgres healthy
```
**装机运行**(进入你本地检出的 patbond-flutter 仓库目录):
```bash
# 模拟器:宿主机地址用 10.0.2.2
flutter run -d <设备ID> \
--dart-define=PATBOND_API_BASE_URL=http://10.0.2.2:8081 \
--dart-define=PATBOND_USER_API_BASE_URL=http://10.0.2.2:8082 \
--dart-define=PATBOND_PET_API_BASE_URL=http://10.0.2.2:8083
# 真机:换成工作机局域网 IP(真机与工作机须同一网络)
# --dart-define=PATBOND_API_BASE_URL=http://<局域网IP>:8081 (其余同理)
```
> 注意:所有 base URL 都要传,漏传的会落到默认 127.0.0.1(指向手机自身)。M3 起若新增服务端口(如 community :8084),相应补 `PATBOND_COMMUNITY_API_BASE_URL`。
---
# M2 挂起项(2026-09-08 登记,待执行)
> 来源:报告 iteration-2/10 §2.1(验收 6)与 iteration-2/12 §3.2;方案 A 挂起决议见 iteration-2/29 §4。
> **时限提醒**iteration-3/06):建议在 **2026-09-21(北极星首次出数日)前完成**,否则首批读数只能标「未验收」。
## 验证一:Android 事件真实落库(~10 分钟)
**目的**:确认埋点链路在真实移动端(platform=android)端到端落库——桌面端已验证全链路仅差 platform 枚举这一步。
**步骤**
1. app 内注册新账号(用户名任意、手机号 11 位、密码 ≥8 位含字母数字),登录进入主页
2. 操作产生事件:切几个 Tab、建一只宠物档案、记一条体重
3. **把 app 退到后台**(Home 键,触发离开前台冲刷),等 5 秒
4. 工作机查库:
```bash
docker exec patbond-postgres-1 psql -U patbond -d patbond -c \
"SELECT event_name, platform, session_id, client_ts
FROM platform.product_events ORDER BY client_ts DESC LIMIT 20;"
```
**通过标准**
- [ ] 有行返回,`platform` 列为 `android`
- [ ] 事件覆盖 ≥3 类(如 page_viewed、pet_create_started/succeeded、health_record_create_succeeded
- [ ] 本轮所有事件共享同一个 `session_id`UUIDv7 格式)
## 验证二:SessionTracker 30 分钟后台换会话(~45 分钟,含等待)
**目的**:验证 10 号报告 §2.1 验收 6——退后台超 30 分钟回前台应更换 sessionId,不超过则沿用。
**步骤**(接验证一,同一次登录、不杀进程):
1. 回前台随便操作一下(记一条体重)
2. **退后台等 5 分钟** → 回前台操作(再记一条体重或切 Tab)
3. **退后台等 35 分钟** → 回前台操作一次
4. 再退一次后台(触发冲刷),等 5 秒后查库:
```bash
docker exec patbond-postgres-1 psql -U patbond -d patbond -c \
"SELECT DISTINCT session_id, min(client_ts) AS first_seen
FROM platform.product_events
WHERE user_id = (SELECT id FROM identity.users WHERE username = '<你的测试用户名>')
GROUP BY session_id ORDER BY first_seen;"
```
**通过标准**
- [ ] 恰好 **2 个** session_id(第 2 步的 5 分钟不换会话、第 3 步的 35 分钟换新)
- [ ] 两个会话的 first_seen 时间差 ≈ 40 分钟(与操作节奏吻合)
**巡检 SQL 兜底**(06 号 §5.1 口径,防「每事件一个 sessionId」缺陷复发):
```bash
docker exec patbond-postgres-1 psql -U patbond -d patbond -c \
"SELECT count(DISTINCT session_id)::float / count(*) AS ratio
FROM platform.product_events;"
# ratio 应远小于 0.9> 0.9 说明 sessionId 生成有问题,告警
```
## 收尾
```bash
cd <你的工作区>/patbond-api && docker compose down
# 测试数据不入库(协作规则 3):本清单产生的数据都在 compose 卷里,
# 需要干净环境时 docker compose down -v 清卷即可
```
两项都过后:填写下方执行记录 + [功能完成清单](feature-checklist.md) 第 9 节「Android 真机落库验证 + SessionTracker 30min 手测」由 🟡 改 ✅。若有任何一项不过,按惯例开缺陷单修复后复测。
### M2 项执行记录
_(待真机到位后填写:日期、设备型号/Android 版本、两项结果、psql 输出摘录(脱敏)、执行人)_
---
# M3 预登记(社区,随迭代交付补全)
以下为 M3 交付过程中预计产生的真机专属验证项,**各工单收口时在此补全具体步骤与通过标准**:
1. **媒体上传弱网表现**T3-13 收口补全,2026-09-09):真机蜂窝/弱 Wi-Fi 下选图→压缩→预签名直传→确认全链路;中断重试不产生孤儿 asset。
**前置**:通用前置准备的后端六容器在位;`PATBOND_MINIO_PUBLIC_ENDPOINT` 必须配置为手机可达地址(工作机局域网 IP:9000,.env 覆盖后重启 compose)——预签名直传 URL 直指 MinIO,漏配则手机端 PUT 必然连不上;`flutter run` 时四个 base URL 全传(含 `PATBOND_COMMUNITY_API_BASE_URL`),media 上传走 user 服务 :8082`PATBOND_USER_API_BASE_URL`)。入口:发布页(T3-17 落地后)九宫格选图。
**步骤与通过标准**
- (a)**蜂窝正常网**:相册多选 3 张 12MP 大图 → 逐格出现进度环且百分比递增(非一跳 100%)→ 全部转 ready;后端 `media.assets` 对应 3 行 `status='ready'`。压缩耗时中端机单张 ≤2s(超出记录机型上报)。
- (b)**弱网中断重试**:开发者选项限速或电梯/地库弱网,上传中开飞行模式掐断直传 → 该格转失败态(红色蒙层 + 重试通栏),其余图不受影响;恢复网络点格内重试 → 转 ready。
- (c)**孤儿不引用**:在(b)失败态与上传中态各尝试一次发布 → 发布钮 gating 拦截(全部 ready 前不可提交);发帖成功后 psql 核对 `community.post_media` 引用的 assetId 全部 `status='ready'`,且不含(b)中断产生的旧 assetId(该行保持 `uploading`,属服务端超时清理范围,不算失败)。
- (d)**凭据过期**:选一张图后挂起 App >10 分钟再恢复触发重试 → 客户端自动换新凭据完成上传(用户无感知,不弹「签名过期」类错误)。
- e**HEIC/方向**iPhone 传输的 HEIC 图与横拍竖拍各一张 → 压缩层统一出 jpeg 且方向正确(服务端 mime 白名单不收 HEIC,此项只能真机验证原生编解码)。
2. **乐观更新真机手感**T3-15/16 收口补全,2026-09-09):点赞/收藏快速连点的合并与回滚动画在真机帧率下的表现;Feed 卡片与详情页跨页状态一致。
**前置**:通用前置准备的后端六容器在位;`flutter run` 时四个 base URL 全传(含 `PATBOND_COMMUNITY_API_BASE_URL=http://<局域网IP>:8084`)。数据:Feed 内至少一条他人发布的帖子(可按 iteration-3 24 号报告 §5a)种子方式造)。
**步骤与通过标准**
- (a)**激活动画帧率**:Feed 卡片与详情页各点赞一次 → 图标同帧翻转 + 240ms 弹性缩放(1→1.25→1)+ 计数即时 ±1;中低端机无可见掉帧或延迟出现的「二次跳动」。取消点赞仅颜色渐出、无缩放。
- (b)**快速连点合并**:同一帖 1 秒内连点点赞 5~6 次 → 视觉每次即时翻转;抓包或服务端访问日志核对该帖 like 端点请求 ≤2 个(单飞 + 最终意图补发);停点后终态与最后一次点击一致,计数与 `GET /api/v1/posts/{id}` 权威值相符。
- (c)**断网回滚**:开飞行模式后点赞 → 图标即时翻转,数秒内**零动画直接跳回**原状态(不得出现「心已灭计数未减」的中间帧或回弹动画)+ SnackBar「操作失败,请重试」恰一条;恢复网络重点 → 正常收敛。
- (d)**跨页一致**:Feed 卡片点赞 → 进详情页应已是激活态;详情页取消收藏 → 返回 Feed 卡片同步取消(同一 ToggleSync 实例,无需刷新)。
- (e)**减弱动态**:系统开启「移除/减弱动画」后点赞 → 状态瞬变、无缩放动画,功能不受影响。
3. **Feed 图片加载**T3-14 收口补全,2026-09-09):真机上滚动 Feed 的图片加载/缓存/占位表现;MinIO 经局域网/公网访问 URL 的可达性差异。
**前置**:通用前置准备的后端六容器在位;`PATBOND_MINIO_PUBLIC_ENDPOINT` 必须配置为手机可达地址(工作机局域网 IP:9000,.env 覆盖后重启 compose)——Feed 卡片封面 URL 是服务端现签的预签名 GET、直指 MinIO,漏配则真机图片全部走失败兜底(`surfaceTint` 底 + pets 图标);`flutter run` 时四个 base URL 全传(含 `PATBOND_COMMUNITY_API_BASE_URL=http://<局域网IP>:8084`)。数据:桌面/工作机先按 iteration-3 24 号报告 §5(a)的种子方式发 ≥26 帖(含单图/多图),保证两页以上可翻。
**步骤与通过标准**
- (a)**首屏与占位**:登录进 Feed → 图片卡先出 `surfaceTint` 加载块(无白闪/布局跳动),随后出图;多图卡右下「+N」角标可读(ink 80% 胶囊白字)。
- (b)**滚动加载**:连续滚到列表底再回顶 → 中低端机不掉帧卡死;回滚经过已看过的图**不重新转圈**(缓存 key 已剥签名参数,同图不同签名命中同一内存缓存——若出现「每次刷新同图重新下载」即为缓存 key 回归,判失败)。
- (c)**下拉刷新后的缓存命中**:下拉刷新(服务端对同一批图重新现签、URL 必然变化)→ 已展示过的封面应即时出图不过转圈;抓包或 MinIO 访问日志核对同对象未重复 GET。
- d**过期 URL 重取**Feed 停留 >1 小时(预签名 TTL)后滚到未加载过的卡 → 旧 URL 过期图走失败兜底属预期,下拉刷新取新签 URL 后恢复出图,无崩溃。
- (e)**可达性差异**:Wi-Fi(局域网 IP)与蜂窝(若 MinIO 未公网暴露)各滚一遍——蜂窝下连不上 MinIO 时应稳定显示失败兜底图标而非无限转圈;记录两种网络的首图出图耗时。
4. **社区事件落库**T3-17 收口补全,2026-09-10):community 域 v3 事件(platform=android)落库观察(沿 M2 验证一的方法,事件名换 v3 增量)。**桌面端不可替代**:Linux 桌面的 `platform=linux` 不在契约枚举内,整批 400 被拒(`analytics_service.dart` 既有预期行为),故 v3 事件的**落库**只能在 Android 上验证;键集与形态的落库正确性已在工作机以 curl 造真实 payload 验证(iteration-3/26 §5c 发布/媒体 8 事件、iteration-3/25 §5c 互动 8 事件)。
**前置**:通用前置准备的后端六容器在位;`flutter run` 时四个 base URL 全传(含 `PATBOND_COMMUNITY_API_BASE_URL`);`PATBOND_MINIO_PUBLIC_ENDPOINT` 配为手机可达地址(媒体三段需真实直传)。可与第 1、2 项同一轮操作合并执行。
**步骤**:登录 → 首页 Feed 滚两屏并下拉刷新一次 → 进一条帖详情点赞/收藏/评论一次 → 返回 → 创作 Tab「发布动态」→ 输入正文 + 选 2 张图 → 「存草稿」一次 → 「发布」→ 回 Feed 确认新帖 → **退到后台等 5 秒**(触发冲刷)→ 工作机查库:
```bash
docker exec patbond-postgres-1 psql -U patbond -d patbond -c \
"SELECT event_name, platform, props FROM platform.product_events
WHERE event_name LIKE 'post\_%' OR event_name LIKE 'feed\_%'
OR event_name LIKE 'comment\_%' OR event_name LIKE 'user\_%'
ORDER BY client_ts DESC LIMIT 40;"
```
**通过标准**
- [ ] (a)**发布漏斗成链**:`post_create_started`(entryPoint=create_tab) → `post_draft_saved`(trigger=manual, mediaCount=2) → `post_publish_succeeded`(fromDraft=true、mediaCount=2、topicCount=0、textLengthBucket、durationMs>0) 三条齐全且 `platform=android`;无 `post_publish_failed`(顺利路径)。
- [ ] (b)**媒体三段逐文件成对**:`post_media_upload_started` / `_succeeded` 各 **2** 条(每张图一条),`sizeBucket` 同一张图的 started/succeeded 取值一致,`durationMs` 为真实上传耗时(非 0);中断重试的那张(与第 1 项(b)合并执行时)另有 `post_media_upload_failed`(failureReason=network_error, attemptSeq=1) + 重试后 started 的 `attemptSeq` 递进。
- [ ] c**隐私红线**:上述 props 中**不含** postId / assetId / commentId / 文件名 / 本地路径 / URL / 精确字数(`textLength`/ 精确字节数(`byteSize`)——出现任一即验收失败(红线 1/2/4)。
- [ ] d**互动与 Feed**`post_liked`/`post_favorited`(source=feed 或 post_detail)、`comment_create_succeeded`、`feed_viewed`(离开 Feed 时一条,impressionCount>0、refreshCount=1)落库;**无** `post_impression`/`post_viewed`(字典锁死为 unknown,若出现即客户端违规)。
- [ ] e**页名归一化**`page_viewed` 出现 `pageName='post_form'`(发布页)与 `'post_detail'`,且 pageName/referrer 中**不含 UUID**。
- [ ] (f)拒绝计数为 0:查 app 日志无 `Analytics batch permanently rejected`,或服务端响应 `rejected=0`(有 rejected 说明事件名/键集与字典不符,属回归)。
## 执行记录(M3
_(待补)_