Date: 2026-09-20 | Status: 全部决策已与用户确认,无未解决 NEEDS CLARIFICATION
本 feature 的关键决策均在头脑风暴阶段与用户逐条确认,来源为对现有代码(017-oauth-login 实现即 InfoUserController / LineCallbackController / OAuthVerifyService)的实地调查。以下记录决策、理由与被否方案。
info_user_devicedevice_id 为主键,一台设备一行(device_id → phone → user_id → 时间戳)。info_user 字段表达不了"设备维度最近一次验证的手机号";独立表 upsert 语义简单(按 PK insert-or-update),不动用户大表。info_user 上加 device_id 字段——只能记"该用户最后登录的设备",语义相反,共用设备会互相覆盖且查不出"设备信任谁";② 复用 info_user_oauth 加 provider='device'——语义混淆,破坏 UNIQUE(provider, provider_uid) 的三方账号语义。均否。oauthLogin 未绑定时若设备有信任记录,返回 {status:"deviceConfirm", tempKey, maskedPhone};App 弹"将绑定 098****789 并登录"确认框,手机号只读不可改,确认后调新接口 oauthDeviceConfirm 完成绑定登录,不发短信。oauth:bind:{tempKey} = provider@providerUid(值格式不变),deviceConfirm 分支额外写 oauth:bind:dev:{tempKey} = deviceId(同 TTL 5 分钟)。oauthDeviceConfirm 校验两键齐全且 dev 值与入参 deviceId 相等。oauthBindPhone 用 indexOf('@') 解析值,若把 deviceId 拼进值里会产生解析歧义与跨流程误用(deviceConfirm 的 tempKey 被喂给 oauthBindPhone 会拼出错误的 providerUid)。正交 key 让现有代码零改动、两种 tempKey 天然隔离。provider@uid@deviceId——解析歧义,需同时改两处解析,否;② tempKey 存 JSON——现有值为纯字符串,引入序列化不一致,否。state 参数state(现状为前端随机串,后端不校验);LineCallbackController.callback 把 state 当 deviceId 用。未绑定分支:设备有信任 → 302 回跳 deviceConfirm=1&tempKey=xxx&maskedPhone=xxx;无信任 → 维持 needPhone=1&tempKey=xxx。已绑定路径也记录设备信任。state 且原样回传,现网已在传(仅作 CSRF 占位,后端未校验);复用它零流程变更,还顺带解决了 017 遗留问题"回调链路没有 App 上下文"。deviceId:可选、string、≤64;缺省/空串/超长一律按未传处理,走原流程。生成与持久化(iOS 建议 Keychain、Android 建议 ANDROID_ID,均跨卸载重装)由 uni-app 前端负责,写入前端契约文档。cid(uni 推送客户端 ID)——重装即变且 cidType 现状传空,稳定性不满足"绑定一次"预期,否;② 后端采集 IP/UA 指纹——不可靠且涉及隐私,否。DeviceTrustService.recordLogin(deviceId, phone, userId)(按 PK upsert,最近一次覆盖),在四处调用:lodeing 成功、oauthLogin 已绑定直登成功、oauthBindPhone 成功、oauthDeviceConfirm 成功;LINE 回调已绑定路径同样调用。InfoUserDeviceMapper extends BaseMapper<InfoUserOauth 同款>,实体 @TableName("info_user_device"),不用 XML resultMap。InfoUserOauthMapper 注释明确"仅用 MP CRUD,无自定义查询、无 XML";本 feature 只有按 PK 的 select/insert/update,MP 原生覆盖。maskPhone 规则InfoUserController.maskPhone(前 3 + **** + 后 4),抽到 DeviceTrustService 共用。oauthDeviceConfirm 复用 oauthBindPhone 的账号校验:status != 0 停用 → no.user.stop 错误;get-or-create(getuser(phone),不存在则按现有规则新建 + 建钱包);期间已被绑定的容错直登也保持一致。