# Face ID 启动锁 — 设计文档

**日期**:2026-05-30(W2)
**作者**:link2026 + Claude
**关联卖点**:#4 隐私三件套(系统级加密 + Face ID + 永久删除)
**优先级**:P1(CLAUDE.md §6 / §8 / §11,原排期 W5 末,提前实现)

---

## 1. 一句话定位

可选的 Face ID/Touch ID 启动锁(默认关)。开启后,冷启动与「后台超过 1 分钟再回前台」都需要系统认证才能进入 App;失败可用设备密码兜底。完全基于系统 `LocalAuthentication`,不自造任何密码学(对齐红线 §10.2)。

---

## 2. 设计决策(已与用户确认)

| 决策点 | 选择 |
|---|---|
| 锁屏时机 | 冷启动 + 后台超过宽限才重锁 |
| 后台宽限 | 60 秒 |
| 认证策略 | `.deviceOwnerAuthentication`(Face ID/Touch ID 优先,自动跳设备密码兜底,避免锁死) |
| 默认状态 | 关(§6) |
| 开关位置 | 「我的」Tab 现有的 Face ID 卡,改为可交互 Toggle |
| 任务切换器隐私遮罩 | 加,**仅锁开启时生效**(进 `.inactive`/`.background` 盖品牌遮罩,防多任务快照泄露;默认关用户无感) |

**关于 §6「截屏黑屏防护…不做」**:那条针对的是**截图防护**(iOS 无官方 API);本设计的任务切换器遮罩是 `.inactive` 盖视图,是官方支持的标准做法,性质不同。

---

## 3. 架构

```
KangkangApp
  └─ WindowGroup { AppLockContainer { RootView() } }   ← 仅包一层,RootView 零改动(§10.7)
                      │
        ┌─────────────┴──────────────────────────────┐
        │ AppLockContainer<Content>                   │
        │  @Environment(\.scenePhase)                 │
        │  渲染 content                                │
        │   .overlay { if isLocked        → LockScreen}│
        │   .overlay { else if showsCover → PrivacyCover}│
        │  onAppear → handleAppear();                 │
        │  onChange(scenePhase) → handleScenePhase()  │
        └─────────────────────────────────────────────┘
                      │ 读写
        ┌─────────────┴──────────────────────────────┐
        │ AppLock.shared  (@MainActor @Observable)     │  ← Security/AppLock.swift
        │  enabled  ←→ UserDefaults("faceIDLockEnabled")│
        │  isLocked / showsPrivacyCover                │
        │  biometryAvailable / biometryLabel           │
        │  gracePeriod = 60s,lastBackgroundedAt        │
        │  authenticate() / enableWithAuth() / disable()│
        └──────────────────────────────────────────────┘
```

单例写法与项目既有 `ModelDownloadService.shared` 一致(`@MainActor @Observable final class` + `static let shared`)。

---

## 4. 触发逻辑(状态机)

| scenePhase / 事件 | 行为 |
|---|---|
| 容器 `onAppear`(冷启动) | `enabled` 为真且尚未冷启动锁过 → `isLocked = true` + 触发认证 |
| `.background` | `lastBackgroundedAt = now`;`showsPrivacyCover = enabled` |
| `.inactive`(任务切换器) | `showsPrivacyCover = enabled && !isLocked` |
| `.active` | 隐藏遮罩;若 `enabled && !isLocked && 离开 > 60s` → `isLocked = true`;若 `isLocked` → 触发认证;清空 `lastBackgroundedAt` |
| 认证成功 | `isLocked = false` |
| 认证失败/取消 | 保持锁定,锁屏提供「解锁」按钮重试(`isAuthenticating` 防重入,不重复弹窗) |

冷启动时 scenePhase 初值为 `.active` 不触发 `onChange`,由 `handleAppear()` 负责冷启动锁;两路触发由 `isAuthenticating` 守卫去重。

---

## 5. 能力探测与兜底

- `refreshAvailability()`:`LAContext.canEvaluatePolicy(.deviceOwnerAuthentication)` → `biometryAvailable`;读 `biometryType` 决定文案(Face ID / Touch ID / 密码)。
- 设备未设密码/无生物识别 → `biometryAvailable = false`,「我的」开关置灰,副标题「本设备未设置 Face ID 或密码」。
- 认证全程系统弹窗;失败/取消不抛错给 UI,只是停留锁屏。

---

## 6. 文件清单

| 文件 | 改动 |
|---|---|
| `康康/Security/AppLock.swift` | **新增**:单例 + LAContext 封装 + 触发逻辑 |
| `康康/Security/AppLockContainer.swift` | **新增**:包裹层 + scenePhase 驱动 + 两个 overlay |
| `康康/Security/LockScreenView.swift` | **新增**:`LockScreenView` + `PrivacyCoverView` |
| `康康/App/KangkangApp.swift` | `RootView()` → `AppLockContainer { RootView() }` |
| `康康/Features/Me/MeView.swift` | 静态 Face ID 卡 → 可交互 Toggle 卡 |
| `康康.xcodeproj/project.pbxproj` | 加 `INFOPLIST_KEY_NSFaceIDUsageDescription`(Debug + Release) |

工程用文件系统同步组,新增 `Security/` 下的源文件自动纳入编译,无需手改 pbxproj 注册。

---

## 7. UI

锁屏(`LockScreenView`,全遮罩,走 Tj tokens):

```
   🔒 (lock glyph)
   康康 已锁定
   你的健康档案已加密保护
   [  Face ID 解锁  ]   ← onAppear 自动触发一次认证;按钮文案随设备能力变
```

隐私遮罩(`PrivacyCoverView`):品牌色底 + app 名,无交互,仅用于遮挡多任务快照。

「我的」Face ID 卡:Toggle 开启时先认证一次(成功才置 `enabled`),关闭直接关。副标题动态:「已开启 · Face ID」/「关闭」/「本设备未设置 Face ID 或密码」。

---

## 8. 红线对齐(CLAUDE.md §10)

- 不自造密码学,只用系统 `LocalAuthentication` ✅
- 默认关,可选开关 ✅
- 不引云 ✅
- 不重构 Tab/RecordSheet 骨架,只加一层包裹 ✅
- 清单内功能(§6/§8/§11 明列 Face ID 启动锁)✅

---

## 9. 测试与验收

- 单元测试价值低(核心是系统弹窗 + scenePhase),不强求;`AppLock` 的宽限判定逻辑可抽纯函数测(可选)。
- **真机验收**:① 开关开启走 Face ID;② 杀进程冷启动需认证;③ 后台 <60s 回来不锁、>60s 回来锁;④ 多任务切换器快照被遮罩;⑤ 关 Face ID 录入(模拟失败)能跳设备密码;⑥ 默认关时全程无感。
- 模拟器:Features → Face ID → Enrolled / Matching Face 可模拟。
