# Client User Identity｜客户端用户标识建立（用于 PUSH Token 绑定）｜Spec

> 阶段：高层规范（spec）
>
> 目标：在**无账号体系或账号可选**的前提下，为客户端生成一个稳定的“客户端用户标识”（下称 `client_user_id`），用于与 APNs/FCM 的 Push Token 建立绑定关系，便于后端精准下发 PUSH，并支持 Token 变更/多设备/环境隔离等场景。

---

## 1. 背景与动机（摘要）

Push Token（APNs device token / FCM registration token）会发生变化（重装、系统升级、重新授权、token rotate 等），且同一用户可能多设备。为了能稳定地“找到这个客户端实例/用户侧主体”并维护 Token 映射，需要一个**与 Token 解耦**、可持久化、不可推断的标识。

---

## 2. 目标（Goals）

- **建立 `client_user_id`**：客户端可生成并持久化一个稳定标识，作为后端 Push Token 绑定的主键之一。
- **Token 绑定可更新**：支持 Token 变更时“同一 `client_user_id` 重新上报即可更新”。
- **多设备兼容**：同一个账号（若未来引入）可关联多个 `client_user_id`；一个 `client_user_id` 可存在多个 Token（例如同设备多渠道/多应用包形态）时需可扩展。
- **环境隔离**：dev/prod、iOS/Android、bundle id / package name 维度隔离，避免串绑。
- **隐私友好**：不使用可追踪的硬件标识（IMEI/IDFA/Android ID 等），不引入额外合规风险。

---

## 3. 非目标（Non-goals）

- 不在本阶段引入完整账号体系、登录态、用户合并策略（如“同一人多设备合并为一个 user_id”）。
- 不在本阶段强制接入设备证明（App Attest/Play Integrity）；仅在安全章节提出可选增强方向。
- 不定义具体数据库表结构与迁移脚本（属于 plan 阶段细化）。

---

## 4. 术语与对象（Definitions）

- **`client_user_id`**：客户端生成并持久化的随机标识，代表“一个客户端安装实例（或一段时间内的用户侧主体）”，用于 Push 绑定。
- **`push_token`**：系统/厂商下发的推送 token（iOS/APNs，Android/FCM），可能变化。
- **`account_id`（可选）**：若未来存在登录账号，则用于把多个 `client_user_id` 归属到同一账号。

---

## 5. 关键决策：使用 UUID 做 `client_user_id` 是否合适？

结论：**合适**，推荐用**随机 UUID（UUID v4 为默认）**，并把它当作后端与客户端都不解析的**不透明字符串**。

### 5.1 为什么 UUID 合适

- **唯一性足够**：v4 基于随机数，碰撞概率极低，满足全局唯一需求。
- **不可推断**：相较自增 ID，不易被枚举；相较设备硬件标识，更隐私友好。
- **跨端易实现**：iOS/Android/JS 都可稳定生成与序列化（字符串）。

### 5.2 需要明确的边界与注意事项

- **UUID 不等于“真实用户”**：它更像“安装实例 ID”。用户重装/清数据后可能变化；这对 Push 绑定通常可接受（新装产生新 token 与新 id）。
- **不要用设备硬件/系统可追踪 ID 替代**：避免隐私与合规风险，也避免系统限制导致的不稳定。
- **安全边界**：如果后端完全信任客户端上报的 `client_user_id`，存在“伪造绑定”风险；应结合登录态或签名/证明（见第 9 节）降低滥用。

### 5.3 UUID 版本建议

- **默认**：UUID v4（实现最简单、兼容最好）。
- **可选增强**：UUID v7（有时间有序性，利于日志/索引与写入局部性），但需要确保两端实现一致与依赖可控。

---

## 6. 客户端行为规范（Client Contract）

### 6.1 生成与持久化

- 首次启动（或首次需要注册 Push 时）：
  - 若本地不存在 `client_user_id`：生成一个新的 UUID（字符串），写入持久化存储。
  - 若已存在：直接复用。
- 存储建议（不做强约束，但必须“尽量稳定”）：
  - iOS：Keychain
  - Android：Keystore 保护的加密存储/SharedPreferences（或等价方案）
  - React Native/Expo：使用安全存储能力（例如 SecureStore/Keychain wrapper）

### 6.2 Push Token 获取与上报时机

- 在以下任一时机触发“注册/更新”：
  - 用户同意 Push 权限后获得 token
  - App 冷启动获取到 token（含 token 变更）
  - 账号登录/登出（若存在账号）
  - 环境切换（dev/prod）或应用更新（可选）

---

## 7. 后端接口契约（API Contract，摘要）

> 具体路由/鉴权方式在 plan 阶段落地；此处先定义字段语义与幂等行为。

### 7.1 注册/更新绑定

- `POST /v1/push/register`
- 请求体（最小集）：
  - `client_user_id`: string（UUID 字符串）
  - `platform`: `"ios" | "android"`
  - `push_token`: string
  - `app_id`: string（bundle id / package name，用于隔离）
  - `env`: `"dev" | "prod"`
  - （可选）`account_id`: string
  - （可选）`device_meta`: `{ model, os_version, app_version, locale, timezone }`
- 行为要求（幂等）：
  - 以 `push_token + env + app_id` 维度做唯一性约束，避免重复记录。
  - 若同一 `client_user_id` 上报了新 token：应更新/新增映射，旧 token 进入失效或保留历史（由实现决定，但必须可控）。

### 7.2 解绑（可选但建议）

- `POST /v1/push/unregister`
- 请求体：
  - `client_user_id`
  - `platform`
  - `push_token`（或让后端按 `client_user_id` 批量解绑，二选一）
  - `app_id`
  - `env`

---

## 8. 数据模型（逻辑约束）

最小需要表达的关系：

- 一个 `client_user_id` 可对应 0..N 个 `push_token`（考虑多端、多渠道、token rotate）。
- 一个 `push_token` 在同一 `env + app_id` 下应只对应一个“当前归属”（避免重复推送）。
- 若存在 `account_id`：
  - 一个 `account_id` 可关联 0..N 个 `client_user_id`（多设备）。

---

## 9. 安全与滥用防护（高层约束）

- **最小要求**：接口需具备基本鉴权与频率限制（例如基于设备指纹/匿名 session/应用侧签名的任一组合），避免被脚本批量绑定垃圾 token。
- **若存在登录态**：推荐绑定写入需要登录态（或在登录后把 `client_user_id` 归属到 `account_id`），降低“抢绑”风险。
- **可选增强（后续）**：接入 iOS App Attest / Android Play Integrity，或对注册请求做一次性挑战签名。

---

## 10. 边界场景与处理原则

- **用户拒绝 Push 权限**：允许只有 `client_user_id`，不产生 token 绑定；后端不应报错。
- **token 变化**：客户端重新调用 `register`，后端必须幂等更新，避免重复推送。
- **重装/清数据**：`client_user_id` 变化可接受；若未来有 `account_id`，可在登录后重新建立关联。
- **多环境**：dev/prod token 不可混用；必须以 `env + app_id` 隔离。

---

## 11. 验收标准（Acceptance Criteria）

- 客户端能稳定生成并持久化 `client_user_id`（重复启动不变）。
- 在 token 获取/变更后，调用注册接口可在后端建立（或更新）绑定关系，且接口幂等。
- 同一 `push_token` 在同一 `env + app_id` 下不会产生多条“当前有效”绑定，避免重复推送。
- 在用户拒绝 Push 权限、无 token 的情况下，不影响 App 正常使用与后续再次授权后的绑定。

