# 017 任务清单

> 顺序执行；每步完成后编译验证。

- [ ] T1 数据模型：`updatesql/sql.md` 加 `info_user_oauth` 建表（含 uk_provider_uid 唯一索引、idx_user_id）。
- [ ] T2 实体：`InfoUserOauth.java`（@TableName=info_user_oauth，字段 id/userId/provider/providerUid/createTime）。
- [ ] T3 mapper：`InfoUserOauthMapper extends BaseMapper<InfoUserOauth>`（无 XML）。
- [ ] T4 LoginUserDto 加 `provider` 字段。
- [ ] T5 JwtUtil.setToken(tokenKey, LoginUserDto) 加 `provider` claim（非空才写）。
- [ ] T6 配置：application.yml 加 `oauth.apple.clientId/jwks-url`、`oauth.google.clientId/tokeninfo-url`、`oauth.line.profile-url`。
- [ ] T7 依赖：ruoyi-admin/pom.xml 加 `nimbus-jose-jwt`。
- [ ] T8 OAuthVerifyService：`verify(provider, credential)` → providerUid；Apple(nimbus 验 ES256)、Google(tokeninfo)、LINE(v2/profile)，校 audience/iss/exp。
- [ ] T9 DTO：OAuthLoginDto、OAuthBindDto。
- [ ] T10 InfoUserController.oauthLogin：校验→查 oauth→命中签 token / 未命中缓存 tempKey 返 needPhone。
- [ ] T11 InfoUserController.oauthBindPhone：取 providerUid + 验短信→关联/新建→写 oauth→签 token。
- [ ] T12 编译验证（JDK21）：ruoyi-admin + ruoyi-system + ruoyi-common。

## 联调待办（开发提供配置后）
- [ ] application.yml 填真实 apple bundleId/clientId、google clientId、line channel。
- [ ] 三家真实 token 端到端验证（Apple 需真机/测试机签 Sign in with Apple）。
- [ ] 前端 app：登录页三按钮 + 首次绑手机流程 + i18n。

## 代码评审修复 (2026-07-30)

评审发现并修复（均已 JDK21 编译通过）：

- [x] **HTTP 超时**：`OAuthVerifyService.httpGet` 原用 `HttpClients.createDefault()` 无超时，provider 慢/不可达会拖垮请求线程池 → 加 `RequestConfig`（连接 5s / 读取 10s / 获取连接 5s）。
- [x] **停用账号拦截**：`oauthBindPhone` 用 `getuser(phone)` 只滤 del_flag、不滤 status，停用账号（status≠0）能通过三方绑定绕过停用 → 加 `status!=0` 拦截（与 `lodeing` / `oauthLogin` 已绑定路径一致），文案复用 `no.user.stop`。
- [x] **Apple audience**：原 `aud.get(0)` 在多 audience 时可能漏判 → 改 `aud.contains(appleClientId)`。
- [x] **i18n 化（裸中文 → message key）**：新增 `no.oauth.*` 共 10 个 key，写入 5 份 properties（`messages` / `zh_CN` / `zh_TW` / `en_US` / `vi`）；`OAuthVerifyService` 异常文案与 `InfoUserController` 提示全部走 `MessageUtils.message`，与 `lodeing` 风格一致。新增 key：
  - 控制器：`no.oauth.provider.blank` / `needphone` / `tempkey.missing` / `tempkey.expired`
  - 校验服务：`no.oauth.credential.blank` / `provider.unsupported` / `token.invalid` / `token.expired` / `audience.mismatch` / `verify.fail`（带 `{0}` 渠道名、`verify.fail` 带 `{1}` 异常详情）
