api-contract.md 8.4 KB

前端接口契约:设备信任免绑手机号(uni-app 用户端)

2026-10-30 增量见文末:同设备换手机号解绑。五字段数据库方案已确认;后端已实现,用户已确认测试库 SQL 执行完成;数据库并发及 App 联调待执行;涉及换号及已有绑定直登的旧描述以增量节为准。

Date: 2026-09-20 | Spec: spec.md | 后端分支: 029-device-trust-phone-bind

本文档是后端对前端的唯一契约。前端按此实施,无需口头追问。所有接口均为现有接口的增量,不传新字段时行为与现网完全一致(老版本 App 无感)。

0. 设备唯一标识 deviceId(前端生成)

  • 定义:App 首次启动时生成的 UUID 字符串,≤64 字符,持久化保存,之后每次登录请求都带上。
  • 持久化建议:
    • iOS:存 Keychain(卸载重装不丢,有现成 uni-app 插件);退化用 storage 也可,只是重装后要重新绑一次。
    • Android:优先取 ANDROID_ID(同设备+同签名跨重装稳定、免权限);退化用 storage 存自生成 UUID。
  • 传参规则:字符串、可选。空串/超长后端按未传处理。不要在未登录的其它业务接口上报。

1. 入参变更(三个现有接口加可选字段 deviceId)

接口 位置 变更
POST /infouser/user/lodeing UserDTO + deviceId(登录成功后端记录设备信任)
POST /infouser/user/oauthLogin OAuthLoginDto + deviceId(未绑定时影响返回分支,见 §2)
POST /infouser/user/oauthBindPhone OAuthBindDto + deviceId(绑定成功后端记录设备信任)

2. oauthLogin 返回新增分支 deviceConfirm

现有返回不变,新增一种:

返回(AjaxResult data) 条件 App 行为
token + 用户信息 三方身份已绑定 直接进 App(现状不变)
{status:"needPhone", tempKey} 未绑定,且设备无信任记录 现有输手机号+短信流程(不变)
{status:"deviceConfirm", tempKey, maskedPhone} 新增 未绑定,且 deviceId 有信任记录 弹一键确认框,见 §3
  • maskedPhone:脱敏手机号,格式 前3位****后4位(如 098****4321),只读展示,不可编辑、不提供换号输入。
  • tempKey 有效期 5 分钟,过期后确认接口返回 no.oauth.tempkey.expired,需重新点三方登录拿新的。

3. 确认弹窗规范

  • 文案示意:将绑定 {maskedPhone} 并登录;按钮:确认 / 取消。
  • 确认 → 调 §4 新接口;取消 → 关闭弹窗回登录页,不产生任何绑定(用户仍可用手机号+短信登录,或换其它三方登录)。
  • 弹窗内不出现验证码输入框、不出现可编辑手机号。

4. 新接口:POST /infouser/user/oauthDeviceConfirm

用途:设备确认分支的一键绑定登录(免短信)。

// 请求体(OAuthDeviceConfirmDto)
{ "tempKey": "abc123...", "deviceId": "uuid-..." }

成功:响应结构与 oauthBindPhone 成功完全一致(msg=登录成功、user 用户信息、token),直接进 App。

失败(AjaxResult error,msg 为国际化文案):

场景 错误 key(参考) App 处理建议
tempKey 缺失 no.oauth.tempkey.missing 提示后回登录页
tempKey 过期/不存在 no.oauth.tempkey.expired 提示"请重新登录",回登录页
deviceId 缺失 / tempKey 不是设备确认凭证 / deviceId 不匹配 / 设备无信任记录 no.oauth.device.mismatch 提示后回登录页(可回落手动绑定流程)
信任手机号账号已停用 no.user.stop 提示账号停用,回登录页
期间该三方身份已被其它端绑定 无错误——直接返回 token 登录成功(容错直登,与 oauthBindPhone 一致) 直接进 App

5. LINE 登录(网页回调链路)

authorize URL 变更(唯一的必改点):state 参数从随机串改为传 deviceId:

https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=...&redirect_uri=...&scope=profile%20openid&state={deviceId}

302 回跳 App(com.twanmsdyh.app://oauthLogin)分支变化:

回跳参数 条件 App 行为
?token=xxx 已绑定 进 App(现状不变)
?needPhone=1&tempKey=xxx 未绑定、无设备信任 现有手机号+短信流程(不变)
?deviceConfirm=1&tempKey=xxx&maskedPhone=098****4321 新增 未绑定、state 携带的 deviceId 有信任记录 弹 §3 同款确认框,确认 → §4 接口
?error=xxx 异常 现状不变
  • maskedPhone 已做 URL 编码,App 解码后展示。
  • 走 POST /oauthLogin(前端自拿 code 的场景,如 H5)时同样受益:body 里带 deviceId 即可,返回分支同 §2。

6. 联调自测清单

  1. 手机号+短信登录(带 deviceId)→ 卸载重装前,用 Apple 新身份登录 → 应弹 deviceConfirm(maskedPhone = 刚登录的号)。
  2. deviceConfirm 确认 → 返回 token,进 App 后"我的"页手机号 = maskedPhone 对应账号。
  3. deviceConfirm 取消 → 再用 Google 新身份登录 → 仍弹 deviceConfirm(未产生绑定)。
  4. 不带 deviceId(模拟老版本)→ 三方新身份登录 → 走 needPhone 原流程。
  5. 等 tempKey 过期(>5 分钟)后点确认 → 收到 no.oauth.tempkey.expired。
  6. LINE:authorize state=deviceId → 已信任设备走 deviceConfirm 回跳分支。

2026-10-30 增量契约:同设备换号解绑

本节优先于上文的旧换号语义;基础请求字段沿用现有接口,不新增接口。后端代码已实现,用户已确认测试库 SQL 已执行,部署及联调状态待核对。

  • 新版本用户 App 的 lodeing、oauthLogin、oauthBindPhone、oauthDeviceConfirm 均提交同一个稳定 deviceId,放在已有 POST body 内;设备 ID 不因退出或换手机号而重建。deviceToken/cid 是推送字段,不能代替 deviceId。LINE 网页回调沿用既有设备传递契约。
  • lodeing:验证成功且手机号从 A 变 B,解除本设备关联的旧用户绑定后,以 B 登录;同号不解绑。
  • oauthBindPhone:验证 B 并绑定登录与手机号登录 B 具有同等效力,允许 B 与设备之前的 A 不同。清理旧绑定后保留本次新绑定。不要求先独立调用 lodeing。
  • oauthLogin:未绑定身份仍返回 needPhone 或 deviceConfirm;已有绑定与本设备当前用户冲突时不直接返回旧用户 token,提示先完成对应手机号验证码登录后重试。不得把旧绑定静默转给其他手机号。
  • oauthDeviceConfirm:确认当前设备手机号,不接受新手机号。用户选择其他手机号时走完整验证码绑定;凭证签发后设备用户已改变则拒绝旧凭证,重新发起 OAuth。
  • 新版本 App 应在换号登录成功后替换本机会话,清理旧账号的本地第三方登录缓存;后端负责解绑服务器记录,不能清除 App 本地状态。
  • 无有效 deviceId 的老版本沿用既有兼容登录,无法保证按设备解绑;不要在前端宣称解绑成功。
  • 成功响应结构保持现有 token/用户信息;新增校验失败沿用项目国际化业务错误包装,错误含义见下表(响应 msg 为国际化文本,key 不作为新增响应字段)。
  • 同账号在设备2登录成功,按现有策略使设备1旧 token 失效;接口401仍按现有方式返回登录页。该行为不等于删除第三方绑定。
场景 后端消息 key / LINE 回跳 App 处理
第三方已有绑定与设备当前手机号冲突 no.oauth.device.phone.changed;LINE 回跳 error=phone_login_required 引导对应手机号验证码登录,再重新发起 OAuth
绑定过程中身份已归属于其他用户,或绑定已被删除 no.oauth.binding.conflict;LINE 回跳 error=fail 不接收旧账号会话,提示重新发起登录
确认凭证设备或签发时用户不匹配 no.oauth.device.mismatch 关闭旧确认页,重新发起 OAuth
tempKey 已消费/过期 no.oauth.tempkey.expired 重新发起 OAuth 获取新 tempKey

确认缓存新增内部键 oauth:bind:user:{tempKey},值为签发时 userId,TTL 同现有键为5分钟;无此快照的旧确认凭证需重新发起 OAuth。前端不传该 userId。新表未创建前不得部署本次代码。