Files
2026-10-22 19:08:46 +08:00

498 lines
31 KiB
Markdown
Raw Permalink 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.
# 智慧后勤收银终端 · 测试角色手册
> 项目:`pj_supermarket_cloud`(智慧后勤 POS 收银终端,Flutter + Android 混合开发)
> 版本基线:v2.1.3+213 · Floor 数据库 v14 · minSdk 24 · Flutter 3.27.4(fvm)
> 本文档是本项目测试角色的**岗位职责 + 测试策略 + 发版流程**三位一体的工作手册。
---
## 0. 角色定位:你在为谁把守发布按钮
**每按下一次"发版",就是把一个新版本同时推进 100 多家商家的收银台。**
收银终端不是普通 App:
- 它是商家的**营业生命线**——收银页白屏 10 分钟,商家 10 分钟收不了钱;
- 它跑在**无人值守的真实硬件**上——双目摄像头、扫码枪、打印机、副屏,任何一个外设失联都是现场事故;
- 它一旦装上就**很难卸载**(Device Owner 模式),发出去的版本没有"撤回"按钮,只有"覆盖再发";
- 它的 bug 大多有**资损属性**——少收钱、重复扣款、订单丢失、离线单不回传,都是直接的经济纠纷。
所以这个岗位的三个铁律:
1. **把每一次发版当成"100 个商家同时投诉"的事故预演。** 测试报告里写的不是"用例通过率",而是"我为什么敢让它上生产的 100 台机器"。
2. **P0 主链路(扫码→加购→结算→支付→出票)任何一条不过,版本就没有出门的资格。** 性能好看、UI 精致都不能换主链路的确定性。
3. **灰度不是走过场。** 100+ 商家意味着你不可能手工覆盖每一家,灰度挑选 + 观察指标 + 回滚预案是你对全部商家负责的机制,而不是仪式。
---
## 1. 被测系统画像(必须烂熟于心)
### 1.1 技术栈与架构要点
| 层 | 技术 | 测试相关要点 |
|---|---|---|
| 框架 | Flutter 3.27.4(**必须用 fvm**) | `~/fvm/versions/3.27.4/bin/flutter`,默认 3.29.2 会令 `flutter_libserialport` 编译失败(Registrar 未解析) |
| 状态/路由 | GetX 4.7.2 | 路由守卫、controller 生命周期竞态是历史 bug 高发区 |
| 网络 | dio 5.7.0 / http | 401/424 守卫、登录页 controller 重建竞态 |
| 消息 | mqtt_client 10.x | clientId/账密创建时固化;配置变化必须重建客户端,否则重连 notAuthorized |
| 本地库 | Floor 1.4.2 + sqflite | 库文件 `cash_register.db`,位于外部 Documents 目录 `pj_cashier/database/`(**非** sqflite 默认目录);版本 **v14 与原生侧 DatabaseHelper 双方锁定**,任何一侧动版本必须两边同改(Floor 遇更高版本会**静默跳过迁移**,这是数据丢失级陷阱) |
| 原生 | facediscern 插件(Java) | 人脸识别引擎、双目摄像头、人脸库、证据图写入、副屏插件 |
| 双屏 | flutter_subscreen_plugin | 副屏四模式(SubDisplayCoordinator);`sendMsgToViceScreen` 永不回包,`sendViceMessage` 由 MainActivity 解包转发立即回包 |
| 串口 | flutter_libserialport | 扫码枪 |
| 打印 | drago_pos_printer(项目携带修复版)+ printing | USB 小票打印机;项目内修复版是因 pub.dev 版 build.gradle 与 AGP 不兼容 |
| 定时任务 | cron 0.6.2 | 终端日志上传、定时探测等后台任务 |
| 语音 | flutter_tts 4.2.2(钉死,Kotlin 编译器约束) | 收银语音播报 |
| 更新 | background_downloader | APK 自动更新;**Device Owner 挡卸载**,只能覆盖安装 |
| 加密 | pointycastle | 通信加密 |
### 1.2 业务模块地图
| 模块 | 路径 | 说明 |
|---|---|---|
| 登录 | `lib/pages/login` | http 守卫 + fenix 重建竞态历史坑 |
| 启动装载 | `lib/pages/load` | 5 步骤装载(含重试防 late final 二次赋值) |
| 收银主界面 | `lib/pages/panel` | 超市收银 + 食堂收银,购物车、结算弹框 |
| 超市订单 | `lib/pages/supermarket_order` | 订单列表/退款 |
| 食堂商品 | `lib/pages/canteen_product` | 商品卡网格(GridView 紧约束历史坑) |
| 食堂预订/取餐/汇总 | `canteen_reserve` / `take_food` / `canteen_order_total` | 预订取餐链路 |
| 卡激活 | `lib/pages/card_active` | 饭卡激活 |
| 订单历史 | `order_history`(+detail) | 本地+云端订单 |
| 设置 | `lib/pages/setting` | 人脸管理、离线用户、同步日志、基本设置(识别摄像头选择等) |
| 终端外壳 | `lib/pages/terminal` | `/terminal` 一级路由(顶栏+二级路由),面板经 arguments 进入 |
### 1.3 本地数据(Floor entities)
订单 `order`、挂单 `order_hold_bill`、超市 SPU/SKU/条码 `supermarket_spu/sku/code`、食堂 SPU `canteen_spu`、分类 `classify`、人脸档案 `face`、用户 `user`、打印历史 `print_history`、定时任务 `scheduled_task(+log)`、文件上传记录 `file_upload_record`、订单凭证上传任务 `order_evidence_upload_task`、设置 `setting`、商铺卡 `shop_card`、广播 `broadcast`。
测试升级/迁移时,这 16 张表**全部**要有存量数据。
### 1.4 外设矩阵
| 外设 | 接入方式 | 测试关键点 |
|---|---|---|
| 双目摄像头(彩色+红外) | 原生 facediscern | facing 元数据不可靠(排线装反过)→ 人工选择摄像头 + NV21 色度探测(U/V 偏离 128 阈值 2.0) |
| 扫码枪 | 串口 | 高频扫码、乱码、拔插 |
| 小票打印机 | USB | 出票内容与格式、断电重连 |
| 副屏 | flutter_subscreen_plugin | 四模式切换、购物车/人脸页副屏内容、10 秒清屏 |
| 语音 | TTS | 播报时机与音量 |
---
## 2. 测试环境建设
### 2.1 设备矩阵(最低配置)
| 档位 | 配置 | 用途 |
|---|---|---|
| **主力真机**(≥1 台) | 项目实际下发的 POS 整机(含双摄、扫码枪、打印机、副屏) | 一切外设/人脸/性能/稳定性测试的**唯一权威环境** |
| 次主力真机(≥1 台) | 另一批次 POS 整机 | 兼容性对照 |
| 低端 Android 平板 | Android 7.x,2GB RAM | 性能下限、兼容性 |
| 模拟器 | Android 14 | 仅限 UI 走查、普通功能回归;**人脸(无摄像头排线)、串口、副屏、Device Owner 在模拟器上不可测或行为失真** |
> 铁律:**外设相关和人脸相关的结论,只认真机。** 模拟器通过 ≠ 真机通过。
### 2.2 构建红线
```bash
# 必须用 fvm 的 3.27.4,且注意 pub get 需国内镜像;gradle daemon 有历史代理坑
~/fvm/versions/3.27.4/bin/flutter analyze # 静态分析,0 error 才准入
~/fvm/versions/3.27.4/bin/flutter test # 单元/组件测试
~/fvm/versions/3.27.4/bin/flutter build apk --release # 发版包
```
- 改过 Floor DAO/Entity 后必须重跑 `build_runner`,否则 `.g.dart` 与 SQL 不一致(历史坑:DAO SQL 改了没重跑,行为与代码不符)。
- 签名必须与线上一致(Device Owner 覆盖安装要求签名一致,否则装不上)。
### 2.3 测试数据准备(每次发版前刷新)
1. **多商户**:至少 3 个测试商家,覆盖 超市模式 / 食堂模式 / 混合。
2. **大商品量商家**:≥ 1 万 SPU(含多级分类、已删除分类下仍挂商品的"脏数据"商家——历史上"全部"标签空白就是这种孤儿分类导致)。
3. **人脸量**:单商家 ≥ 3000 人脸档案(压人脸同步与引擎装载)。
4. **存量库**:一台装着上一版生产数据(订单 ≥ 5000 条、挂单、打印历史齐全)的真机,专用于升级测试。
5. **钱包类型**:饭卡/消费/充值三种钱包类型(walletTypeUniqueCode 1/2/3)都要有测试账户。
### 2.4 环境分层
| 环境 | 用途 | 要求 |
|---|---|---|
| dev | 日常功能测试 | 后端可随时重启,允许脏数据 |
| test | 集成回归 | 数据形态贴近生产 |
| 预发 | 发版前全量回归 | 后端为发版候选版本;**后端先行部署**(增量同步依赖后端 updateTime 契约,前后端版本必须配套验证) |
| 生产灰度 | 3–5 家真实商家 | 见 §6 发布流程 |
---
## 3. 测试类型全景
### 3.1 功能测试(按模块的核心用例要点)
**A. 登录与启动(P0)**
- 冷启动 → Load 页 5 步骤装载(任何一步失败重试后可恢复;不出现 late final 二次赋值崩溃)。
- 登录成功/失败/断网登录;token 过期后 401/424 守卫(登录页自我替换 + fenix 重建竞态:表单有字仍提示"请输入账号"就是复发了)。
- 三处登录后跳转路径都带 currentRoute 守卫。
**B. 收银主链路(P0,最高优先级)**
- 扫码枪扫码加购(串口数据流)、手工选商品加购、数量步进器 +/-、改价、折扣。
- 食堂模式:餐段限制、菜谱日期范围、预订取餐核销。
- 购物车边界:0 元单、大数量、清空、快速连续加购(连击去抖)。
**C. 结算弹框专项(本项目历史 bug 密集区,必须逐条过)**
| 检查项 | 要点 |
|---|---|
| 金额快照 | 弹框金额是**冻结快照**;识别用户重算后必须 `updateCheckoutSnapshot` 推送(body 订阅 revision) |
| 会员恢复 | 重开弹框时会员必须恢复(initialUser 种子化+刷钱包);挂单恢复时会员也要恢复 |
| 钱包类型 | 饭卡(1)/消费(2)/充值(3) 三种类型展示正确;食堂内嵌按饭卡展示 |
| 数量步进器 | 只有传 `onQuantityChanged` 才有 +/−;回调必须返回**新快照** |
| 额外支付 | 额外支付金额计入快照;`_computedAmountMealPrice` 不得残留旧值 |
| 行内确认 | 破坏性操作是行内二次确认(5 秒超时还原),不是再套弹框 |
| 就地注册人脸 | 弹框内只做本地引擎装载(rebuildFaceLibrary + registrationStream 进度),**绝不拉取远端** |
**D. 支付(P0)**
- 现金、扫码支付(被扫/主扫)、人脸支付(识别→比对→扣款→凭证图留存)。
- 支付成功→副屏成功页→10 秒清屏;打印小票。
- 支付失败/超时/取消的三态展示与重试。
- **资损专项**:同一单不重复扣款;断网瞬间支付结果未知时,重试不产生双单;退款金额 ≤ 原单。
**E. 人脸(P0,混合开发重点)**
- 人脸同步三模式:full / incremental / surgical(带 spuIds 定向);同步进度条两阶段语义正确。
- 人脸档案管理页:删除/停用后引擎库即时生效。
- 人脸质量上报:V2 历史数据(updateTime=null)装新包后点一次 V3 同步即回填修复。
- 识别链路:活体(RGB/IR)、防伪、识别后会员信息与钱包正确。
- **副屏盲区专项**:结算弹框打开时副屏购物车间歇更新——人脸消息曾被 `isAvailable` 门禁静默丢弃,验证按需补探测生效。
**F. 商品同步(P0)**
- 增量同步三模式 + 墓碑(逻辑删除):后台改价/上新/删除 → 终端 ≤ 1 个同步周期内生效(删除是逻辑删除,须先补 update_time 才能被增量感知)。
- 超市"全部"标签不空白(孤儿分类口径:商品挂已删除分类时 INNER JOIN classify 后的行为)。
- **扫码枪路径不过分类过滤**,条码直查必须命中已删除分类下的商品(这是口径例外,验证它没被"顺手修掉")。
- 同步日志页可查每次同步的结果与耗时。
**G. 离线与回传(P0)**
- 断网收银正常落本地 → 网络恢复后离线订单回传,不丢不重。
- **sysType 陷阱回归**:设备切模式后,离线订单按订单自身 sysType 回传,不得按设备当前模式过滤(历史 bug:过滤导致查空假成功——回传"成功"但后台查无此单)。
**H. 挂单/取单**
- 挂单(含会员)→ 取单恢复购物车+会员+额外支付;挂单列表分页。
**I. 订单与退款**
- 订单历史(本地/云端)、订单详情、退款(整单/部分)、退款凭证。
**J. 设置与终端**
- 基本设置:识别摄像头选择(自动/彩色/红外,CameraSelectionStore -1=自动,探测只在 FACE_EXECUTOR)、打印设置、语音设置。
- 终端外壳 `/terminal`:顶栏状态栏、二级路由;panel 经 arguments 进终端(**内层路由不能用 Get.arguments**)。
- 离线用户管理、同步日志查看。
### 3.2 性能测试
**指标与参考门槛**(基线由首次全量测量建立,此后每版对比,回归 > 20% 必须给出解释):
| 指标 | 参考门槛 | 测量方法 |
|---|---|---|
| 冷启动 → 登录页可见 | ≤ 3 s | `adb shell am start -W` + 录屏逐帧 |
| 登录 → 可收银(含人脸库装载) | ≤ 10 s | 录屏 + 应用内日志时间戳 |
| 加购点击 → 购物车刷新 | ≤ 200 ms(P95) | 录屏逐帧 / DevTools timeline |
| 结算弹框打开 | ≤ 500 ms | 同上 |
| 人脸识别(刷脸→出会员) | ≤ 1.5 s | 现场秒表 + 日志 |
| 扫码→商品上屏 | ≤ 300 ms | 高频连扫 100 次取分布 |
| 打印指令→出纸 | ≤ 3 s | 现场实测 |
| 滚动流畅度 | FPS ≥ 50,无连续丢帧 | `adb shell dumpsys gfxinfo com.example.pj_supermarket_cloud framestats` |
| 内存 | 长跑 24h 无持续增长趋势 | `adb shell dumpsys meminfo com.example.pj_supermarket_cloud`,每小时采样 |
| ANR | **0 次** | logcat + `/data/anr/` |
| 1 万 SPU 增量同步 | ≤ 60 s(基线待建) | 同步日志页耗时 |
**工具链**:
```bash
# 内存
adb shell dumpsys meminfo com.example.pj_supermarket_cloud
# 帧渲染
adb shell dumpsys gfxinfo com.example.pj_supermarket_cloud framestats
# 帧率/卡顿(Perfetto)
~/Library/Android/sdk/cmdline-tools/latest/bin/adb shell perfetto ... # 或 Android Studio Profiler
# 真库拉取后用 sqlite3 实测慢查询(先 adb root)
adb root && adb pull /storage/emulated/0/Android/data/com.example.pj_supermarket_cloud/Documents/pj_cashier/database/cash_register.db /tmp/
sqlite3 /tmp/cash_register.db ".tables"
sqlite3 /tmp/cash_register.db "EXPLAIN QUERY PLAN <待测SQL>"
```
外加:**Flutter DevTools**(Performance / Memory / CPU Profiler,重点看 build 帧与 Isolate 占用)。
**本项目性能地雷区**(新版本改动到这些区域,性能测试自动升级为 P0):
1. **人脸链路**:历史上 getFaceStatus 在主线程全表扫描导致 ANR;现状契约是状态查询**绝不 SELECT faceModel** 大字段、重活进 FACE_EXECUTOR、占线短路(进度键 total/successCount)。凡动 face 相关 DAO/原生代码,必做 3000+ 档案真机压测。
2. **结算链路**:后端 createOrder 历史上是 9 次串行 Feign 的大事务;前端虽已去重+内存缓存,但任何弹框重算逻辑的改动都可能把耗时打回来。结算弹框打开耗时每版必测。
3. **同步链路**:拼音转 Isolate、详情分批 100、删除分批 500——改动分批参数/ Isolate 策略要回归 1 万 SPU 商家。
4. **DB 迁移**:升级路径必然触发迁移,必须带存量库测启动耗时。
### 3.3 混合开发专项(Flutter ↔ Android 通道)
这是本项目区别于普通 Flutter 项目的测试核心:
- **MethodChannel 契约测试**:`FlutterFaceDiscernPlugin` / `FlutterFaceSubScreenPlugin` / `FlutterTool` / `FlutterFaceReceiver` 每个对外方法的入参、返回、异常路径逐一验证;协议字段名(如 updateCard 系列)**一字不能改**,通道两侧是弱契约,编译器不查。
- **线程纪律**:原生重活必须走 FACE_EXECUTOR;任何"顺手"在主线程做的事(DB 查询、图片 IO)都是 ANR 候选。
- **双库版本锁**:Floor v14 与原生 DatabaseHelper v14 必须一致。发版前 diff 两边版本号;**Floor 遇到更高版本号会静默跳过迁移**,这会直接造成数据结构错位。
- **生命周期**:人脸页副屏自建 Texture 的创建/销毁;App 后台→前台、跳转打断识别(isRecognition 复用为锁 + dispatchId 令牌防迟到回调)。
- **消息回路**:副屏 `sendViceMessage`(MainActivity 解包转发、立即回包)与 `sendMsgToViceScreen`(永不回包)行为不同,别混用验收标准。
- **原生日志**:logcat 过滤 `facediscern` 标签,验证引擎状态迁移。
### 3.4 稳定性测试
- **长跑(Soak)**:真机收银页 24–48h:每 10 分钟一次自动加购+结算(可用 adb 脚本或人工节奏),期间跑 3 轮人脸同步。验收:零 crash、零 ANR、内存曲线平稳、副屏内容不漂移。
- **Monkey**:`adb shell monkey -p com.example.pj_supermarket_cloud --throttle 300 -s <seed> 100000`,仅限冒烟后(人脸页、设置页单独跑),验收零 ANR、crash 可归因。
- **异常注入**(每版必做一轮):
- 收银过程中拔网线 / 关 WiFi / 热点切换(弱网可用 `adb shell tc qdisc` 限速/加丢包);
- 扫码枪/打印机/副屏中途拔插;
- 支付进行中按返回/进后台/锁屏;
- 断电重启(非正常关机):重启后本地订单完好、离线单可回传、无 double init;
- 后端接口 500/超时/返回畸形 JSON。
- **MQTT 稳定性**:改密/换地址后重连(历史坑:clientId 固化导致 notAuthorized 拒绝);后台 pull 指令 10 分钟内到达;**publish 断连时是静默 return 且恒返成功**,不能只看 App 日志判断发送成功,要以后台收到为准。
### 3.5 兼容性测试
- 机型:≥2 批次 POS 整机 + 1 台低端平板 + 1 台主流手机(用于老板/运维装 App 看后台的场景)。
- 系统:Android 7 / 9 / 12+(minSdk 24 到 targetSdk 全区间至少抽 3 档)。
- **副屏分辨率**:不同分辨率/长宽比副屏下布局不错位(副屏内容是独立渲染路径)。
- **大字号**:主题字号运行时缩放 1.2–1.5 倍(forui 字号被 sizeScalar 缩放),全页面走查无裁切/溢出——本项目已有多起字号裁切历史(DropdownButton、商品卡、输入框)。
- 深浅色/横竖屏(若支持)。
### 3.6 网络与离线测试
- 弱网矩阵:200ms 延迟 / 5% 丢包 / 30KB 带宽,核心链路(登录、加购、结算、同步)逐一过。
- 断网下可用功能清单明确(本地商品、本地收银、挂单、订单历史),不可用功能有**明确友好提示**而非转圈。
- 恢复网络后:离线订单自动回传(不丢/不重/顺序合理)、增量同步自动追平、MQTT 自动重连且**鉴权成功**。
- 账密错误/设备未授权/token 过期的错误路径展示。
### 3.7 升级与数据迁移测试(100+ 商家场景的重中之重)
- **覆盖安装**:上一生产版(带真实存量数据)→ 候选版,验证:
1. 安装成功(Device Owner 下只能覆盖装,签名必须一致);
2. 启动后 DB 迁移正确:16 张表数据完整(订单/挂单/人脸/设置逐一核对条数);
3. Floor 与原生两侧版本一致,无静默跳迁移;
4. 登录态/设置项保留。
- **跨版本升级**:至少支持 n-2 → n(很多商家不会及时更新)。
- **首启动修复逻辑**:V2 人脸数据(updateTime=null)在装新包后点一次 V3 同步被回填。
- **降级禁止**:明确记录不支持降级;若需回滚,走"覆盖安装回旧版 + 旧版能读新库结构吗"的预研结论(通常不能,所以灰度观察窗口才是主防线)。
- 自动更新链路:background_downloader 下载 → 安装提示 → 覆盖安装成功;下载中断续传、磁盘不足提示。
- **旧 APK 窗口判别**:灰度观察期区分"设备没更新"(后台看在线设备 APK 版本)与"更新了但坏了"。
### 3.8 外设专项
| 外设 | 必测点 |
|---|---|
| 双目摄像头 | 彩色/红外自动探测标签正确(U/V 偏离 128 阈值 2.0);人工选择后重启仍生效;排线装反的机器上靠人工选择兜底 |
| 扫码枪 | 高频连扫、破损码、含中文码、扫码中拔出(不 crash,插回自动恢复) |
| 打印机 | 小票内容字段齐全(商家名/单号/明细/金额/时间)、缺纸提示、连续打印 100 单不掉 USB |
| 副屏 | 四模式切换、收银副屏跟车、支付成功页 10 秒清屏、副屏拔插 |
| TTS | 播报内容、与系统音源冲突(放音乐时播报) |
### 3.9 安全测试
- 日志脱敏:人脸照片、密码、token 在日志中只出现脱敏格式(历史规范:密码只打脱敏格式)。
- 本地 DB 中人脸特征/用户钱包数据的存储边界;DB 文件 pull 下来检查敏感字段。
- 通信加密(pointycastle)生效性抽验(抓包不可读业务明文——脱敏字段除外)。
- 证据图(人脸支付对比凭证、OSS 上传)不越权可访问;OSS 上无空文件/损坏图(历史坑:oss 有空文件图)。
- 更新包完整性:APK 下载校验。
### 3.10 自动化与单元测试
现状:`test/` 下 3 个测试文件(terminal_toolbar、mq_supermarket_product_msg、search_highlight_tool)。
- CI 门槛:`flutter analyze` 0 error + `flutter test` 全绿,作为提测准入。
- **建议逐步补齐**(按资损优先级):
1. 金额计算纯函数(快照重算、额外支付、钱包优先级);
2. 离线订单回传的 sysType 归类逻辑;
3. 增量同步三模式的本地合并/墓碑逻辑;
4. MethodChannel 消息的序列化/反序列化 round-trip(可用原生侧单测)。
- 自动化是辅助,**主链路验收永远是真机手工**(外设决定了无法完全自动化)。
### 3.11 UI/UX 走查
- 大字号缩放 1.2/1.5 两档全页面走查(见 3.5)。
- 弹框类:showAppDialog 内 controller 生命周期(历史上 await 返回后立刻 dispose 会崩,弹窗内输入框 controller 收进弹窗 StatefulWidget)。
- 图片:appImg 有宽度契约(width 透传),Row 中图片无界会 "forces an infinite width" 崩溃——新页面必查。
- 加载态/空态/错误态三态齐全;Toast 长文案不截断(需 Flexible)。
---
## 4. P0 回归红线(发版冒烟集,30 分钟内完成)
> 每版发版前在一台**带全部外设的 POS 真机**上,用**生产形态的测试商家**过完以下清单。任何一条 FAIL 即停发。
1. 冷启动 → Load 装载 → 登录成功。
2. 扫码枪扫 3 个不同商品 → 购物车正确(名称/价格/数量)。
3. 修改数量(步进器+直输)→ 金额重算正确。
4. 挂单 → 继续收银新单 → 取单 → 购物车+会员+额外支付完整恢复。
5. 选会员(刷脸识别会员)→ 会员价生效 → 钱包余额正确显示(三种钱包类型各一次)。
6. 现金支付一单 → 出票 → 订单历史可见。
7. 扫码支付一单(真实支付通道/沙箱)→ 支付成功页 → 副屏 10 秒清屏 → 出票。
8. 人脸支付一单 → 识别 ≤ 1.5s → 扣款 → 证据图留存可在后台看到。
9. 断网收银一单 → 恢复网络 → 订单回传后台可查(**去后台亲眼确认,不信本地"成功"日志**)。
10. 退款一单 → 金额正确 → 小票/后台同步。
11. 后台改价一个商品 → 触发/等待同步 → 终端价格更新;后台删一个商品 → 终端不再可售。
12. 超市"全部"标签有数据、食堂商品页正常。
13. 重启 App → 登录态/设置/本地订单保留。
14. 覆盖安装(上一版→本版,带存量库真机)→ 启动无迁移报错,订单/挂单/人脸档案条数不丢。
15. 弱网(限速)下完成一次完整收银。
16. 副屏四模式切换一遍,内容正确。
17. logcat 过滤 30 分钟无未归因 crash/ANR。
18. 终端日志上传到后台可见(本版日志)。
---
## 5. 缺陷管理与取证规范
### 5.1 分级
| 级别 | 定义 | 处置 |
|---|---|---|
| S1 | 资损风险(重复扣款/丢单/金额错)、无法收银(白屏/崩溃/主链路卡死)、数据丢失 | 立即停发/回滚;24h 内修复验证 |
| S2 | 主链路降级(某支付方式不可用、同步停摆、副屏黑屏)、ANR | 阻塞发版 |
| S3 | 次要功能缺陷、特定机型问题 | 可带病发版但需灰度期确认不恶化,记录在发版说明 |
| S4 | 文案/体验 | 排期修复 |
### 5.2 取证标准(缺一不可)
1. **构建指纹**:版本号 + git commit + 构建时间。复测前**核对堆栈行号/build 号确认装的是新包**——历史上有"修了没生效,其实装的是旧 APK"的事故。
2. **录屏**:从进入页面到 bug 发生完整过程。
3. **日志**:app 内日志文件(`pj_cashier/` 下日志目录,注意带 ANSI 色码需 `sed` 清洗)+ logcat 原始文件。
4. **DB 副本**:事发后立刻 `adb root && adb pull` 真库(很多订单类 bug 的真相只在库里)。
5. 复现步骤(含前置数据:商家/会员/商品)、机型/系统/网络环境。
6. 偶现问题给出复现概率(n 次中出现 m 次)。
### 5.3 测试报告(每版必产出)
- 版本号/commit/包来源;
- 冒烟集 18 条结果表;
- 性能对比表(本版 vs 上版基线,标注 >20% 回归项);
- 缺陷清单(级别/状态/是否阻塞);
- **发版建议**:Go / No-Go / Go with conditions(列条件与观察项);
- 灰度观察计划(挑哪些商家、看什么指标、多长时间)。
---
## 6. 发布流程:对 100+ 商家负责的机制
```
提测准入(analyze+test 全绿) → 全量回归(§3+§4) → 测试报告(Go 建议)
→ 灰度发布(3–5 家) → 观察窗口 24–48h → 达标 → 全量推送
↘ 异常 → 停止推送 + 回滚预案
```
### 6.1 灰度商家挑选原则(覆盖风险面,不是随机)
1. 1 家**关系友好、反馈快**的商家(首发哨兵);
2. 1 家**大商品量**(≥1 万 SPU)超市;
3. 1 家**食堂模式** + 1 家**人脸支付高频**商家;
4. 1 家**上一版就没及时更新**的商家(覆盖跨版本升级路径)。
### 6.2 观察窗口必看指标(每天早晚各巡一次)
- **后台设备在线率与 APK 版本分布**(区分"没更新成功"和"更新了坏了");
- 终端日志上传是否持续可见(日志断流 = 终端或 MQTT 出事);
- 后台订单量/金额曲线与该商家日常对比(掉量 = 收银受阻);
- 离线订单回传成功率;
- 主动联系哨兵商家 1 次:开机速度、扫码、刷脸、打印有无异常。
### 6.3 停发/回滚触发条件(事先约定,不临场辩论)
- 任一 S1;或灰度商家中出现无法归因的 crash;
- 在线率异常下跌(对照历史同时段);
- 回滚手段:停止推送 + 向灰度设备覆盖安装上一稳定版 APK(签名一致可覆盖;注意降级后 DB 结构兼容性预研结论)。
### 6.4 版本节奏约定
- 后端先行:涉及同步契约(updateTime/ids/墓碑、MQTT topic 40 spuIds)的版本,**后端先部署,前端后发**;
- 收尾字段:发版说明必须写清"本版改动的外设/通道/DB 版本"三项,供测试和售后对照。
---
## 7. 历史事故库(本项目专属回归清单)
> 这些坑都在本仓库真实发生过。改动相关区域时,对应条目自动进入本版回归范围。
| # | 事故 | 根因摘要 | 触发回归的条件 |
|---|---|---|---|
| 1 | 人脸页 ANR | getFaceStatus 主线程全表扫描 faceModel | 改 face DAO/原生查询 |
| 2 | 登录页"请输入账号"假提示 | http 守卫对 /login 自我替换 + fenix 重建空实例竞态 | 改 http 守卫/登录跳转 |
| 3 | MQTT 重连 notAuthorized | clientId/地址创建时固化,复用旧客户端带新账密 | 改 MQTT 配置/重连逻辑 |
| 4 | 离线单"回传成功"但后台查无 | 按设备当前 sysType 过滤致查空假成功 | 改离线回传/模式切换 |
| 5 | 离线单假成功(同上族) | 日志只落 warning+,本地看不到 | 排查手法:真库核对 |
| 6 | 超市"全部"标签空白 | 商品挂已删除分类,视图按活跃分类分组(口径不一致) | 改 classify/商品查询 SQL(**必须重跑 build_runner**) |
| 7 | 结算弹框金额不对 | 弹框是冻结快照,识别重算后未推送 | 改弹框重算逻辑 |
| 8 | 弹框会员丢失 | payUser 是运行时态被清空,业务层 payUser 才是事实来源 | 改弹框开关/挂单恢复 |
| 9 | 钱包类型显示错 | 本地 WalletType 枚举缺 TOPUP,未同步后端 code | 后端加钱包类型/改展示 |
| 10 | 副屏停在购物车间歇 | 人脸消息被 isAvailable 门禁静默丢弃(仅定时任务置 true) | 改副屏消息链路 |
| 11 | 人脸质量不上报 | V2 写入 updateTime=null 永不上报 | 改人脸同步/上报 |
| 12 | "修复没生效" | 装的其实是旧 APK(复测未核对 build 号) | **每次复测** |
| 13 | 双库静默跳迁移 | Floor 遇更高版本号跳过迁移,与原生版本错位 | 任何一侧动 DB 版本(两侧必须同改 v14→vN) |
| 14 | 覆盖安装失败 | 签名不一致 + Device Owner 挡卸载 | 换签名/发版渠道 |
| 15 | 商品卡溢出/字号裁切 | GridView 紧约束 + forui sizeScalar 运行时缩放 | 改卡片布局/字号 |
| 16 | 图片页崩溃 | appImg 无界 Row(width 契约被破坏) | 新增带图页面 |
| 17 | 编译失败(Registrar 未解析) | 用了非 fvm 的 Flutter 版本 | 本机/CI 环境变更 |
| 18 | 后台拉日志无反应 | 旧 APK 窗口 或 publish 断连静默 return 恒返成功 | 改 MQTT 发送/定时任务 |
---
## 8. 附录
### 8.1 发版 Checklist(打印贴工位)
- [ ] `flutter analyze` 0 error、`flutter test` 全绿
- [ ] fvm 3.27.4 构建 release 包,签名与线上一致
- [ ] P0 冒烟集 18 条全过(真机+全外设)
- [ ] 覆盖安装升级测试过(n-1 和 n-2 存量库)
- [ ] Floor/原生双库版本核对一致
- [ ] 性能对比无 >20% 回归(或已归因说明)
- [ ] 24h 长跑零 crash/ANR
- [ ] 灰度商家名单已定(含哨兵/大商品量/食堂/人脸高频/滞后更新各 1)
- [ ] 回滚 APK 与预案就绪
- [ ] 测试报告已发(含 Go/No-Go 结论)
- [ ] 发版说明含外设/通道/DB 版本三项变更声明
### 8.2 灰度观察日报模板
```
日期: 版本: 观察人:
灰度商家在线情况:(设备数/版本分布/与昨日对比)
日志上传:正常/断流(断流设备:xxx)
订单与回传:总单数 xx,离线回传失败 x 笔(明细)
crash/ANR:0 起 / xx 起(归因:xxx)
哨兵商家回访:xxx
结论:继续观察 / 全量 / 停发回滚(理由)
```
### 8.3 常用命令速查
```bash
FLUTTER=~/fvm/versions/3.27.4/bin/flutter
PKG=com.example.pj_supermarket_cloud
$FLUTTER analyze && $FLUTTER test && $FLUTTER build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apk
adb shell dumpsys meminfo $PKG | head -30 # 内存
adb shell dumpsys gfxinfo $PKG framestats # 帧渲染
adb logcat -d | grep -E "FATAL|ANR|facediscern" # 崩溃/ANR/原生
adb shell monkey -p $PKG --throttle 300 100000 # Monkey
# 真库取证
adb root && adb pull /storage/emulated/0/Android/data/$PKG/Documents/pj_cashier/database/cash_register.db /tmp/
# 弱网注入
adb shell tc qdisc add dev wlan0 root netem delay 200ms loss 5%
adb shell tc qdisc del dev wlan0 root
```
---
*本文档随版本演进持续维护:每次事故复盘后更新 §7;每次性能全量测量后更新 §3.2 基线。文档本身过时 = 测试体系过时。*