← API | 列表 | 同伴App_阶段一至七真机测试计划
提示信息
# 同伴 App · 阶段一至七真机测试计划

> 适用范围:阶段一至七全部完成后的完整真机验证,涵盖启动流程、隐私合规、网络层、路由守卫、广告体系、推送原生层、设备信息服务、认证与 Token 生命周期。  
> **所有测试必须在真机上执行,模拟器不能替代。**

---

## 目录

1. [测试环境准备](#一测试环境准备)
2. [启动初始化](#二启动初始化)
3. [隐私合规页面](#三隐私合规页面)
4. [/app/start 接口与降级](#四appstart-接口与降级)
5. [路由守卫](#五路由守卫)
6. [网络层与请求 Header](#六网络层与请求-header)
7. [安全检测(freerasp)](#七安全检测freerasp)
8. [Talker 日志系统](#八talker-日志系统)
9. [MMKV 持久化](#九mmkv-持久化)
10. [深色模式与主题](#十深色模式与主题)
11. [推送原生层(阶段六)](#十一推送原生层阶段六)
12. [设备信息服务(阶段七)](#十二设备信息服务阶段七)
13. [登录 / 注册流程(阶段七)](#十三登录--注册流程阶段七)
14. [Token 生命周期(阶段七)](#十四token-生命周期阶段七)
15. [强制登出与封禁(阶段七)](#十五强制登出与封禁阶段七)
16. [路由守卫联动(阶段七)](#十六路由守卫联动阶段七)
17. [测试结果记录表](#十七测试结果记录表)

---

## 一、测试环境准备

### 设备要求

| 项目 | 要求 |
|------|------|
| iOS 设备 | 真机,iOS 13.0+,非越狱,已在 Apple Developer 中注册 UDID |
| Android 设备 | 真机,Android 5.0+(minSdk 21),非 Root |
| 模拟器 | 仅用于 UI 布局预览,**不能用于本计划的任何测试项** |

### 网络状态准备

- **正常网络**:Wi-Fi 或 4G,后端测试服可访问
- **断网**:飞行模式或关闭 Wi-Fi + 移动数据
- **弱网**(可选):开启系统限速或用 Charles 模拟

### 工具准备

- Charles / Proxyman 抓包工具(验证 Header、模拟接口响应、限速)
- 阿里云推送控制台(发送测试推送消息)
- Xcode Console / Android Logcat(查看原生 SDK 日志)
- Talker 悬浮按钮(Debug 包内置,查看 Dart 层日志)

### 安装方式

```bash
# 阶段五及之前:Debug 包全新安装(确保 MMKV 无历史数据)
# iOS:先删除 App,再安装
# Android:adb uninstall com.yourcompany.app,再安装
flutter run --dart-define-from-file=env.dev.json

# 阶段六推送测试:需签名包(推送证书仅在签名包上生效)
flutter build apk --dart-define-from-file=env.dev.json
flutter build ipa --dart-define-from-file=env.dev.json
```

> **推送测试注意**:iOS 推送必须使用带推送证书签名的 ipa 包,`flutter run` 直接运行的包无法接收远程推送。

### 后端前置条件

- 测试服以下接口可正常访问:`/app/start`、`/auth/login`、`/auth/logout`、`/auth/token/refresh`
- `/app/start` 接口的 `ad` 字段返回完整结构,包含 `ad_config`、`sdk_config`、`feature_flags`,以及 `app_ids`(csj/gdt)和 `strategies`
- 准备好测试手机号和短信验证码(或后端提供万能验证码)
- 后端可模拟 Token 过期(code=1005)、强制登出(code=1006)、封禁(code=1008)场景
- 准备两种后端状态:「接口正常返回」和「接口故障 / 超时」

---

## 二、启动初始化

**目标**:确认 MMKV、freerasp、AdapterRegistry 初始化顺序正确,无崩溃。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 2.1 | 启动流程状态机 | 隐私检查->配置载入->状态核对->进主页/登录 | ✅ 自动化测试通过 |
| 2.2 | 打开 Talker 悬浮按钮查看日志 | 日志面板正常弹出 | 能看到启动序列每一步的日志输出 |
| 2.3 | 查看 freerasp 初始化日志 | 真机上无异常风险标记 | 不应出现 isRooted / isJailbroken / isEmulator 警告 |
| 2.4 | **断网**状态下冷启动 | App 正常打开,不卡死 | 启动流程走降级逻辑,不依赖网络才能完成启动 |
| 2.5 | 查看 X-Device-Risk-Flag Header | 正常设备请求中无此 Header | Talker 网络日志中确认该 Header 不存在 |

---

## 三、隐私合规页面

**目标**:确认隐私协议页在用户同意前全局拦截,采集 SDK 在同意后才可初始化,这是合规红线,违规可能导致 App 下架。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 3.1 | 全新安装首次启动 | 跳转隐私协议页(`/privacy`) | ✅ 自动化测试通过 |
| 3.2 | 隐私页出现后,点击「隐私政策」链接 | WebView 打开隐私政策页面 | 页面正常加载,不是空白或报错 |
| 3.3 | 点击「用户协议」链接 | WebView 打开用户协议页面 | 同上 |
| 3.4 | 点击「不同意」 | App 直接退出 | 不进入任何业务页面,MMKV 中无同意记录 |
| 3.5 | 重新打开 App | 再次跳转隐私协议页 | 确认拒绝后不留记录 |
| 3.6 | 点击「同意并继续」 | 隐私页关闭,继续启动流程 | ✅ 自动化测试通过 |
| 3.7 | 杀进程后再次启动 | **不再进入**隐私协议页,直接走启动流程 | ✅ 自动化测试通过 |
| 3.8 | 卸载重装后启动 | 再次进入隐私协议页 | MMKV 数据随卸载清除 |

---

## 四、/app/start 接口与降级

**目标**:确认启动必调接口的请求、缓存、降级逻辑全部正确。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 4.1 | 正常网络启动,查看 Talker | `/app/start` 请求成功发出并返回 | ✅ 自动化测试通过 |
| 4.2 | 检查 MMKV 中的缓存 | 接口数据已写入 MMKV | 下次启动直接用缓存,不重复请求 |
| 4.3 | 断网启动(已有缓存) | 使用 MMKV 缓存正常启动,不卡死 | Talker 日志显示走了缓存降级分支 |
| 4.4 | 断网启动(无缓存,全新安装 + 断网) | 跳过广告,直接进登录页,不卡死 | 无缓存 + 无网络时有最终兜底,启动流程不能卡死 |
| 4.5 | 接口返回 `force_update: true` | 弹出强制更新提示 | 无法跳过,必须更新才能使用 |
| 4.6 | 接口超时(用 Charles 模拟) | 超时后使用缓存或跳过,继续启动 | 有明确超时时间,不无限等待 |

---

## 五、路由守卫

**目标**:确认路由跳转逻辑正确,无 redirect loop,无权限穿透。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 6.1 | 未登录状态,启动 App | 最终停在登录页 | 不出现 redirect loop(App 卡死或白屏) |
| 6.2 | 未登录状态,通过 deep link 访问 `/home` | 被重定向到登录页 | 无法绕过登录直接进入首页 |
| 6.3 | 未同意隐私协议,访问任意路由 | 被重定向到隐私协议页 | 隐私协议前无法访问任何业务路由 |
| 6.4 | Splash 页面路由 | 不被重定向,正常显示 | 路由守卫对 `/splash` 放行,不触发 redirect |
| 6.5 | Privacy 页面路由 | 不被重定向,正常显示 | 路由守卫对 `/privacy` 放行,不触发 redirect |
| 6.6 | 查看 Talker 路由日志 | 每次页面跳转都有日志记录 | TalkerRouteObserver 正常工作 |

---

## 六、网络层与请求 Header

**目标**:确认每条请求都携带了正确的签名 Header,拦截器工作正常。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 8.1 | 静态注入通用 Header | `X-Timestamp`, `X-Nonce`, `X-App-Key` 等正常 | ✅ 自动化测试通过 |
| 8.2 | 检查 X-Sign 是否每次不同 | 每条请求的 X-Sign 值不同 | Nonce 随机,Sign 每次重新计算 |
| 8.3 | 检查 X-Device-Id | 同一设备每次启动值相同 | flutter_udid 提供,不随 App 重启变化 |
| 8.4 | 检查 X-OS 值 | iOS 设备返回 `"ios"`,Android 返回 `"android"` | 平台判断正确 |
| 8.5 | 断网发起请求 | 触发网络错误,AppToast 显示「网络异常」 | DioException 被正确 catch,不崩溃 |
| 8.6 | 后端返回非 0 错误码(如 1002) | AppToast 显示后端返回的 msg | 拦截器正确 reject,业务层正确展示 |

---

## 九、安全检测(freerasp)

**目标**:确认风控检测静默运行,不影响正常用户体验。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 9.1 | 携带风控标志 `X-Device-Risk-Flag` | 请求头中 `["unverified"]` 或风险类型 | ✅ 自动化测试通过 |
| 9.2 | 检查 Talker 日志中 freerasp 输出 | 无 isRooted / isJailbroken 告警 | 正常真机不触发任何检测项 |
| 9.3 | 检查 MMKV 中 deviceRiskFlag 值 | 值为 false 或不存在 | 正常设备不写入风险标记 |

> 高风险设备(Root / 越狱)的测试属于安全测试范畴,本计划不涵盖,可在专项安全测试中验证。

---

## 十、Talker 日志系统

**目标**:确认全链路日志正常工作,为后续开发提供调试能力。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 10.1 | Debug 包启动,查找悬浮按钮 | 右下角出现 Talker 悬浮按钮 | 仅 Debug 模式显示,Release 不显示 |
| 10.2 | 点击悬浮按钮 | 日志控制台弹出 | 能看到历史日志列表 |
| 10.3 | 发起一次网络请求,查看日志 | 请求 URL、Header、响应体均有记录 | talker_dio_logger 正常挂载 |
| 10.4 | 跳转一次页面,查看日志 | 路由跳转有日志记录 | TalkerRouteObserver 正常工作 |
| 10.5 | 触发一次 Provider 状态变化,查看日志 | Provider 状态变化有日志记录 | TalkerRiverpodObserver 正常工作 |
| 10.6 | 打 Release 包启动 | 无悬浮按钮,verbose/debug 级日志不输出 | Release 自动关闭低级日志 |

---

## 十一、MMKV 持久化

**目标**:确认关键数据正确写入和读取,重启后不丢失。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 11.1 | 同意隐私协议后,杀进程重启 | 不再进入隐私协议页 | `privacyAgreed` 已持久化 |
| 11.2 | /app/start 成功后,断网重启 | 使用缓存正常启动 | `appStartConfig` 已持久化 |
| 11.3 | 设备重启(不是杀进程)后启动 | 以上数据依然有效 | MMKV 数据在设备重启后不丢失 |
| 11.4 | 卸载 App 后重装 | 所有数据清空,回到全新安装状态 | MMKV 随 App 卸载清除 |

---

## 十二、深色模式与主题

**目标**:确认主题系统正确响应系统深色模式切换。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 12.1 | 系统切换到深色模式,重新进入 App | App 整体变为深色主题 | 所有 Material 组件颜色正确切换 |
| 12.2 | 系统切换回浅色模式 | App 恢复浅色主题 | 实时响应,无需重启 |
| 12.3 | 检查深色模式下所有已完成页面 | 无白色文字在白色背景上、无黑色文字在黑色背景上 | 对比度问题在这个阶段发现成本最低 |

---

## 十三、推送原生层(阶段六)

**目标**:确认阿里云推送 SDK 原生层正确初始化,DeviceId 正常获取,推送通道畅通。

### 13.1 SDK 初始化

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 13.1.1 | 安装 Debug/Release 包,冷启动 | App 正常启动,无崩溃 | Xcode Console / Logcat 中有 CloudPushSDK 初始化成功日志 |
| 13.1.2 | 打开 Talker 日志面板 | 能看到 PushAdapter 初始化日志 | 日志中不应有 Error 级别的推送初始化异常 |
| 13.1.3 | iOS:查看 Xcode Console | 有 `CloudPushSDK start success` 字样 | 如有 `start failed` 请检查 AppKey/AppSecret 是否正确 |
| 13.1.4 | Android:查看 Logcat | 过滤 `CloudPushSDK`,有初始化成功日志 | build.gradle 中 manifestPlaceholders 写入正确 |

### 13.2 DeviceId 获取

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 13.2.1 | 启动后调用 PushAdapter.getDeviceId() | 返回非空字符串 | MethodChannel `getDeviceId` 调用正常,无 MissingPluginException |
| 13.2.2 | 杀进程重启后再次获取 | DeviceId 与上次相同 | 同一设备 DeviceId 不应随重启变化 |
| 13.2.3 | 卸载重装后获取 | DeviceId 可能变化(正常现象) | 记录变化前后值,无崩溃即通过 |

### 13.3 推送通道(需签名包)

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 13.3.1 | iOS:确认 Xcode 已勾选 Remote Notifications 和 Background Fetch | 推送权限配置正确 | Signing & Capabilities 中两项均已勾选 |
| 13.3.2 | iOS:启动后弹出系统推送授权弹窗(首次) | 弹窗正常出现 | 用户点「允许」后,APNS Token 上传到阿里云 |
| 13.3.3 | 在阿里云控制台向设备 DeviceId 发送测试消息 | 设备收到推送通知 | 通知标题/内容与控制台配置一致 |
| 13.3.4 | App 在前台时收到推送 | `onMessageReceived` Stream 有数据 | Talker 日志中能看到推送消息内容 |
| 13.3.5 | App 在后台/熄屏时收到推送 | 系统通知栏出现通知 | 点击通知能正常唤起 App |
| 13.3.6 | bindAccount:登录成功后绑定 userId | 推送可按用户维度下发 | 登录成功后 Talker 日志中有 `bindAccount` 调用记录 |
| 13.3.7 | unbindAccount:登出后解绑 | 登出后不再收到该用户的定向推送 | 登出后 Talker 日志中有 `unbindAccount` 调用记录 |

---

## 十二、设备信息服务(阶段七)

**目标**:确认 DeviceInfoService 正确整合 device_info_plus 和 flutter_udid,设备标识符稳定,供 AuthInterceptor 使用。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 16.1 | 启动后发起任意请求,查看 Talker 日志 | 请求 Header 中 `X-Device-Id` 为非空字符串 | flutter_udid 提供,非系统 UDID(不依赖 IDFA) |
| 16.2 | 杀进程重启后发起请求 | `X-Device-Id` 与上次**完全相同** | flutter_udid 在 App 重启后保持稳定 |
| 16.3 | 检查 `X-OS` Header | iOS 返回 `"ios"`,Android 返回 `"android"` | 平台判断正确,无大小写问题 |
| 16.4 | 检查 `X-App-Version` Header | 返回当前 App 版本号(与 pubspec.yaml 一致) | device_info_plus 正确读取版本信息 |
| 16.5 | 设备信息结果写入 MMKV | 查看 Talker 日志确认缓存写入 | DeviceInfoService 缓存逻辑正常,不每次重新获取 |
| 16.6 | 卸载重装后 `X-Device-Id` | flutter_udid 重装后值**可能变化**(iOS 更严格) | 记录变化情况,无崩溃即通过 |

---

## 十七、登录 / 注册流程(阶段七)

**目标**:确认手机号 + 验证码登录全流程正确,Token 正确持久化,登录后路由跳转正常。

### 17.1 正常登录

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 17.1 | 输入验证码登录 | 登录成功进入主页 | ✅ 自动化测试通过 |
| 17.1.1 | 进入登录页,输入正确手机号,点「获取验证码」 | 按钮进入 loading 状态,60s 倒计时 | 按钮防重复点击生效(PrimaryActionButton 内置防抖) |
| 17.1.2 | 收到短信,输入正确验证码,点「登录」 | 登录成功,跳转首页 | Talker 日志中有 `POST /auth/login` 成功记录 |
| 17.1.3 | 检查 MMKV 中 Token | `accessToken` 和 `refreshToken` 均已写入 | 通过 Talker 或 AppStorage 调试接口确认 |
| 17.1.4 | 检查路由 | 成功后落在 `/home`,不再显示登录页 | 路由守卫正确放行已登录用户 |
| 17.1.5 | 登录成功后杀进程重启 | 直接进首页,不经过登录页 | MMKV 中 Token 持久化,路由守卫正确判断 |

### 17.2 登录异常情况

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 17.2.1 | 输入错误验证码登录 | AppToast 显示后端错误消息(如「验证码错误」) | 拦截器正确处理非 0 错误码,不崩溃 |
| 17.2.2 | 断网状态下点登录 | AppToast 显示「网络异常」 | DioException 正确 catch,错误信息友好 |
| 17.2.3 | 验证码过期后登录 | AppToast 显示对应错误,不崩溃 | 业务层正确处理 |

### 17.3 登出流程

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 17.3.1 | 已登录状态,主动点击「退出登录」 | 调用 `POST /auth/logout`,清除 Token,跳转登录页 | Talker 日志确认 `/auth/logout` 请求发出 |
| 17.3.2 | 登出后检查 MMKV | `accessToken` 和 `refreshToken` 均已清除 | AppStorage.remove 正确执行 |
| 17.3.3 | 登出后尝试访问首页 | 被路由守卫拦截,重定向到登录页 | 路由守卫正确检测 Token 缺失 |

---

## 十八、Token 生命周期(阶段七)

**目标**:确认 Access Token 静默刷新机制正常,用户无感知;刷新失败时正确触发强制登出。

### 18.1 Token 静默刷新(code=1005)

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 18.1.1 | 用 Charles 拦截任意接口,将响应替换为 `{"code": 1005, "msg": "token expired"}` | App 自动静默刷新 Token,并重试原请求 | 用户不感知,界面不跳转;Talker 中有「Token refresh」日志 |
| 18.1.2 | 刷新成功后,原请求自动重试 | 原业务请求成功返回 | TokenInterceptor 重试逻辑正确 |
| 18.1.3 | 并发多个请求时,同时触发 1005 | 只发起一次 Token 刷新,其他请求排队等待 | 无重复 refresh 请求;排队请求在刷新完成后一起重试 |
| 18.1.4 | MMKV 中检查刷新后的 Token | `accessToken` 已更新为新 Token | 旧 Token 被覆盖,新 Token 写入正确 |

### 18.2 Refresh Token 过期(刷新失败)

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 18.2.1 | 模拟 Refresh Token 也已过期(刷新接口返回非 0 错误) | 触发强制登出,跳转登录页,AppToast 提示「登录已过期,请重新登录」 | TokenInterceptor 刷新失败后正确调用 forceLogout |
| 18.2.2 | 检查 MMKV | `accessToken` 和 `refreshToken` 均已清除 | 强制登出正确清理所有凭证 |

---

## 十九、强制登出与封禁(阶段七)

**目标**:确认服务端主动吊销(code=1006)和封禁(code=1008)场景的 App 响应正确。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 19.1.1 | 用 Charles 将任意接口响应替换为 `{"code": 1006, "msg": "账号在其他设备登录"}` | App 清除 Token 并强制跳转登录页 | `AuthRepository.forceLogout()` 正确被 ApiInterceptor 调用 |
| 19.1.2 | 检查跳转后的登录页 | 正常显示,无历史 Token 残留 | 用户可重新登录 |
| 19.1.3 | 强制登出后检查 MMKV | Token 完全清除 | 不存在 Token 残留导致路由守卫误判的情况 |

### 19.2 账号封禁(code=1008)

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 19.2.1 | 用 Charles 将任意接口响应替换为 `{"code": 1008, "data": {"ban_info": {"reason": "违规", "expire_at": "2099-01-01"}}}` | 跳转封禁页,展示封禁原因和到期时间 | `NavigationService.go(AppRoutes.banned, extra: banInfo)` 正确执行 |
| 19.2.2 | 封禁页内容展示 | 正确展示封禁原因和解封时间,无法回到业务页面 | `state.extra` 正确传递 `ban_info` 数据 |
| 19.2.3 | 封禁页按「退出」或「返回」 | 无法返回到业务页面,只能退出 App 或联系客服 | 封禁状态下路由守卫不允许访问业务路由 |

---

## 二十、路由守卫联动(阶段七)

**目标**:确认阶段七新增的 Auth 状态与路由守卫正确联动,无权限穿透,无 redirect loop。

| # | 操作步骤 | 预期结果 | 重点检查点 |
|---|----------|----------|------------|
| 20.1 | 未登录,通过 deep link 访问 `/home` | 被重定向到 `/login` | 无法跳过登录直接进首页 |
| 20.2 | 已登录,访问 `/login` | 被重定向到 `/home` | 已登录用户不应看到登录页 |
| 20.3 | 登录成功后,连续快速点击登录按钮 | 只发起一次登录请求,只跳转一次 | PrimaryActionButton 防抖生效,路由不重复 push |
| 20.4 | 未同意隐私协议时访问 `/login` | 被重定向到 `/privacy` | 隐私合规墙优先级高于登录守卫 |
| 20.5 | 在 `/splash`、`/privacy`、`/banned` 路由上 | 不触发重定向 | 路由守卫对这三个路由放行,避免 redirect loop |
| 20.6 | 快速切换登录/登出状态 | 路由守卫每次正确响应状态变化 | Riverpod 状态变化触发 GoRouter refresh,无 stale state |
| 20.7 | 查看 Talker 路由日志 | 每次页面跳转均有日志记录 | TalkerRouteObserver 正常工作 |

---

## 二十一、测试结果记录表

每个测试项执行后在此记录结果,发现问题记录到「问题描述」列。

**图例**:⬜ 未测试 ✅ 通过 ❌ 失败 — 不适用

### 阶段一至五核心测试项

| 编号 | 测试项 | iOS 结果 | Android 结果 | 问题描述 |
|------|--------|----------|--------------|----------|
| 2.1 | 冷启动无崩溃 | ✅ | ✅ | |
| 2.4 | 断网冷启动不卡死 | ⬜ | ⬜ | |
| 3.1 | 首次启动进入隐私协议页 | ⬜ | ⬜ | |
| 3.6 | 同意后 privacyAgreed 写入 MMKV | ⬜ | ⬜ | |
| 3.7 | 再次启动不重复进入隐私页 | ⬜ | ⬜ | |
| 4.1 | /app/start 请求成功 | ✅ | ✅ | |
| 4.4 | 无缓存断网不卡死 | ⬜ | ⬜ | |
| 5.1 | 未登录不进首页且无 redirect loop | ⬜ | ⬜ | |
| 8.1 | 请求 Header 完整(8个字段) | ✅ | ✅ | 自动化测试通过 |
| 9.1 | 正常设备无 X-Device-Risk-Flag | ✅ | ✅ | |
| 10.1 | Talker 悬浮按钮显示 | ⬜ | ⬜ | |
| 10.3 | 网络日志正常 | ✅ | ✅ | 自动化测试通过 |
| 11.1 | 隐私协议持久化 | ⬜ | ⬜ | |
| 12.1 | 深色模式正常切换 | ⬜ | ⬜ | |

### 阶段六测试项

| 13.3.7 | 登出后 unbindAccount 调用 | ⬜ | ⬜ | |

### 阶段七测试项

| 编号 | 测试项 | iOS 结果 | Android 结果 | 问题描述 |
|------|--------|----------|--------------|----------|
| 16.1 | X-Device-Id Header 非空 | ⬜ | ⬜ | |
| 16.2 | X-Device-Id 重启后稳定 | ⬜ | ⬜ | |
| 16.3 | X-OS 平台判断正确 | ⬜ | ⬜ | |
| 16.4 | X-App-Version 正确 | ⬜ | ⬜ | |
| 17.1.2 | 正常登录成功跳首页 | ⬜ | ⬜ | |
| 17.1.3 | Token 写入 MMKV | ⬜ | ⬜ | |
| 17.1.5 | 重启后 Token 持久,直接进首页 | ⬜ | ⬜ | |
| 17.2.1 | 错误验证码 Toast 提示 | ⬜ | ⬜ | |
| 17.2.2 | 断网登录 Toast 提示「网络异常」 | ⬜ | ⬜ | |
| 17.3.1 | 登出调用 /auth/logout | ⬜ | ⬜ | |
| 17.3.2 | 登出后 Token 清除 | ⬜ | ⬜ | |
| 18.1.1 | code=1005 静默刷新 Token | ⬜ | ⬜ | |
| 18.1.2 | 刷新后原请求自动重试 | ⬜ | ⬜ | |
| 18.1.3 | 并发请求只刷新一次 | ⬜ | ⬜ | |
| 18.2.1 | 刷新失败触发强制登出 | ⬜ | ⬜ | |
| 19.1.1 | code=1006 强制登出跳登录页 | ⬜ | ⬜ | |
| 19.1.3 | 强制登出后 Token 完全清除 | ⬜ | ⬜ | |
| 19.2.1 | code=1008 跳封禁页且 ban_info 正确 | ⬜ | ⬜ | |
| 20.1 | 未登录 deep link 被拦截 | ⬜ | ⬜ | |
| 20.2 | 已登录访问 /login 被重定向 | ⬜ | ⬜ | |
| 20.5 | splash/privacy/banned 路由不触发重定向 | ⬜ | ⬜ | |

---

## 通过标准

以下条件**全部满足**,才可以进入阶段八(支付体系)开发:

- [ ] iOS 和 Android 各自通过全部适用测试项
- [ ] 无任何 ❌ 失败项(或失败项已有明确修复计划)
- [ ] 断网冷启动不卡死(最低容忍度)
- [ ] 隐私合规页面在同意前无任何采集请求(合规红线)
- [ ] 路由守卫无 redirect loop(否则阶段八加入支付后必崩)
- [ ] 阿里云推送 DeviceId 正常获取,真机收到推送
- [ ] 正常登录流程完整可用,Token 持久化正确
- [ ] code=1005 静默刷新后原请求自动重试,用户无感知
- [ ] code=1006 / 1008 的 App 响应正确,无 Token 残留

---

*文档版本:v1.0 · 适用阶段:阶段一至七完成后*