# 017 实施计划

## 架构：最大化复用现有登录链路

```
provider 凭证 ──▶ OAuthVerifyService.verify(provider, credential) ──▶ providerUid
                                                                        │
                ┌───────────────────────────────────────────────────────┘
                ▼
        查 info_user_oauth (provider, provider_uid)
        ├─ 命中 → 取 InfoUser → 校 status/del_flag → issueToken(provider) → 返回
        └─ 未命中 → Redis 缓存 {provider,providerUid}(tempKey,TTL5m) → 返回 needPhone+tempKey
                                                                        │
                ┌───────────────────────────────────────────────────────┘
                ▼  (前端引导输手机号 → getcode 发短信)
        oauthBindPhone(tempKey, phone, code)
          取 providerUid + 验短信(复用 lodeing: redis code==input || "8888")
          getuser(phone):
            ├─ 已注册 → 关联(写 oauth)
            └─ 未注册 → createUser(phone) 新建 → 写 oauth
          → issueToken(provider) → 返回
```

- **token 签发**：复用 `JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, loginDto)`，仅给 `LoginUserDto` 加 `provider` 字段并在 setToken 写入 claim。
- **新建用户**：复用 `createUser` 的「建 InfoUser + 建钱包 + 删旧 token」逻辑；昵称=手机号不变。
- **短信**：复用 `getcode` 发送 + `lodeing` 的校验（key=phone 去+，万能码 8888）。

## 数据模型

```sql
CREATE TABLE info_user_oauth (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id      BIGINT       NOT NULL COMMENT '关联 info_user.user_id',
  provider     VARCHAR(16)  NOT NULL COMMENT 'apple/google/line',
  provider_uid VARCHAR(64)  NOT NULL COMMENT '三方稳定用户ID(Apple sub / Google sub / LINE userId)',
  create_time  DATETIME     DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_provider_uid (provider, provider_uid),
  KEY idx_user_id (user_id)
) COMMENT='三方账号绑定';
```

## API 契约

### 1) `POST /infouser/user/oauthLogin`  (`@Anonymous`)
请求 `OAuthLoginDto`：
```
provider     apple|google|line
credential   identityToken(Apple) / idToken(Google) / accessToken(LINE)
cid, cidType, deviceToken, voIPToken   // 推送字段，与现有登录一致
```
响应（命中）：
```
{ code:200, msg, data: InfoUser, token }   // token claim provider=本次渠道
```
响应（未命中，首次）：
```
{ code:200, msg, data:{ status:"needPhone", tempKey } }
```

### 2) `POST /infouser/user/oauthBindPhone`  (`@Anonymous`)
请求 `OAuthBindDto`：
```
tempKey, phone, code   // code=短信验证码
cid, cidType, deviceToken, voIPToken
```
响应：`{ code:200, msg, data: InfoUser, token }`

## 三家凭证校验（OAuthVerifyService）

| provider | 凭证 | 校验 | 取 providerUid | 依赖 |
|---|---|---|---|---|
| apple | identityToken(ES256 JWT) | 拉 `appleid.apple.com/auth/keys`(JWKS) 验签 + 校 iss/aud/exp | `sub` | nimbus-jose-jwt（新增） |
| google | ID-Token | GET `oauth2.googleapis.com/tokeninfo?id_token=` 取 claims + 校 aud=clientId | `sub` | httpclient4（已有） |
| line | access_token | GET `api.line.me/v2/profile`(Bearer) | `userId` | httpclient4（已有） |

> Google 走 tokeninfo HTTP 而非 firebase-admin，因后者需初始化 FirebaseApp+服务账号（项目未配置），tokeninfo 零配置即可。

## 文件清单

| 类型 | 文件 |
|---|---|
| 新增实体 | `ruoyi-system/.../domain/InfoUserOauth.java` |
| 新增 mapper | `ruoyi-system/.../mapper/InfoUserOauthMapper.java`（BaseMapper，无 XML） |
| 新增校验服务 | `ruoyi-admin/.../utils/oauth/OAuthVerifyService.java` |
| 新增 DTO | `ruoyi-admin/.../user/dto/OAuthLoginDto.java`、`OAuthBindDto.java` |
| 改 controller | `ruoyi-admin/.../user/InfoUserController.java`（+2 方法） |
| 改 token | `ruoyi-common/.../model/LoginUserDto.java`、`ruoyi-system/.../utils/JwtUtil.java` |
| 改配置 | `ruoyi-admin/src/main/resources/application.yml`（oauth 段） |
| 改依赖 | `ruoyi-admin/pom.xml`（+nimbus-jose-jwt） |
| SQL | `updatesql/sql.md` |

## 风险 / 待验证

- Apple / Google / LINE 的凭证校验**需用各家真实 token + 正确 clientId/bundleId 联调**才能端到端验证；配置值（clientId/jwks url）由前端/运营提供后填入 application.yml。
- Apple JWKS 建议加缓存（key 偶尔轮换）；初版每次拉取或加内存缓存。
