# 存量用户与业务身份映射

## 原则

网云穿用户原来的隧道、设备、套餐、余额/订单、权限等仍由网云穿数据库管理。本轮平台接入不能重新生成一套业务用户 ID，再把老用户当新用户处理。

- 平台长期身份键为 `(issuer, sub)`。
- 产品原用户 ID 作为不透明字符串使用，前导零和超出 JavaScript 安全整数范围的 ID 都不能丢失。
- 产品代码固定 `wyc`；服务端不能接受客户端提交任意产品代码来切换身份。
- 不迁移旧密码；已有手机号必须由业务方确认可信，正式导入需要显式确认。
- 一个手机号不代表可以自动合并同一产品的多个历史账号，冲突须隔离处理。

## 导入文件

UTF-8 CSV 固定表头：

```csv
product_code,external_user_id,phone_e164,display_name,source_created_at
wyc,000123456,+8613800000000,示例用户,2024-01-02T03:04:05+08:00
```

手机号采用 E.164；示例不是待导入的真实用户。先在隔离环境使用脱敏/合成数据演练，真实导出文件按个人信息保护要求保存，不提交 Git、不暴露在公开文档目录。

## 两阶段 CLI 操作

以下命令由平台运维执行，不是开放给产品任意写入映射的 HTTP API。

```bash
# 默认仅校验并生成脱敏报告
./ssoctl users import --file wyc-users.csv

# 核对 Dry Run 报告后，在正确的数据库配置下执行
./ssoctl users import --file wyc-users.csv \
  --apply --trust-all-phones \
  --sha256 SHA256_FROM_DRY_RUN
```

Dry Run 与 Apply 必须使用同一文件；文件变动需重新核对 SHA256。正式执行前备份、核对目标库，保留导入批次和报告。执行后复核成功数、冲突数与总数，抽查产品 ID 映射，并重复执行确认不会新增重复账号。

## 处理规则

| 输入情况 | 平台处理 |
| --- | --- |
| 合法、唯一手机号且不存在平台账号 | 创建 pending_activation 用户并预建映射 |
| 已有平台手机号 | 附加产品映射，仍须核对原绑定 |
| 同产品重复手机号 | 隔离冲突，不自动合并 |
| 同外部 ID 对应不同手机号 | 隔离冲突 |
| 空/非法手机号 | 隔离，不能导入成正常用户 |
| 同批次重跑 | 幂等续跑，不重复创建 |

预建用户没有密码，也没有被伪造的协议接受记录。首次仍须短信或微信认证、接受当前协议，之后才从 `pending_activation` 激活并继续原授权事务。

若某产品开启“强制绑定微信”，手机号预导入和短信认证仍不会伪造微信身份。用户必须实际完成公众号扫码，并验证预导入的原手机号后才可完成该产品授权；身份冲突继续进入隔离流程，不自动生成或合并账号。

## 产品回调如何绑定

Client 配置了 `product_code=wyc` 且用户存在该产品映射时，已验证的 ID Token / UserInfo 可能包含：

```json
{
  "sub": "00000000-0000-4000-8000-000000000001",
  "tuyi_product_code": "wyc",
  "tuyi_product_user_id": "000123456"
}
```

产品服务端按顺序处理：

1. 先完整验证 OIDC 身份，不能仅解码 JWT，也不能相信客户端转述的 claims。
2. 已有 `(issuer, sub)` 绑定时，使用该业务用户；若与新声明的旧 ID 矛盾，拒绝并记录冲突。
3. 无绑定但有本产品旧 ID 声明：查找对应老业务用户，检查未被另一个 sub 绑定；在数据库事务中加唯一约束后建立映射。
4. 没有旧 ID 声明：**不能直接推断这是新用户**，也可能是未导入或导入冲突的老用户。迁移期先走核查/补全流程，再按产品明确的新注册策略处理。
5. 绑定成功后用原业务用户 ID 创建产品会话，不改资产归属。

建议产品映射表至少对 `(issuer, sub)` 和本业务用户 ID 建立符合业务的一对一唯一约束；重复回调和并发首次登录只能建一条映射。

## 放量与回滚

先完成两次隔离演练，再内部账号、1%、10%、30%、100% 分阶段放量，每阶段观察业务高峰。旧登录入口及回退开关保留，出现映射错误立即停止放量，不在线执行自动账号合并。

手机号改变、禁用及会话撤销通过[签名事件](WEBHOOKS.md)通知产品，但产品仍需撤销自己的会话。恢复演练需确认平台用户、映射和产品业务资产一致；不能只验证“数据库能启动”。
