spec.md 15 KB

Feature Specification: 设备信任免绑手机号(同设备三方登录免重复绑定)

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

Feature Branch: 029-device-trust-phone-bind

Created: 2026-09-20

Status: Draft

Input: User description: "登录时记录设备唯一标识 deviceId(App 首启生成持久化传入)。同一设备任一登录流程验证过手机号(短信登录/三方绑号/三方已绑定直登)即记录 deviceId→手机号信任;之后该设备上三方登录遇未绑定身份时返回脱敏手机号一键确认(只读、免短信),确认后直接绑定并签发 token。只改后端并输出前端契约文档;SQL 写入 updatesql/sql.md。"

User Scenarios & Testing (mandatory)

User Story 1 - 同设备三方登录免短信一键绑定 (Priority: P1)

用户一直在某台手机上用"手机号+短信验证码"登录 App。某天他在这台手机上点了 Apple / Google / LINE 登录(该三方身份从未绑定过)。按现状他必须重新输手机号、收短信、输验证码。本功能后:App 弹出"将绑定 098****789 并登录"的一键确认框(手机号只读、不可修改),点"确认"即完成三方身份与该手机号账号的绑定并直接登录,全程无需短信。

Why this priority: 这是本功能的核心价值——消除"同一台设备重复绑手机号"的摩擦;没有它,其余部分没有意义。

Independent Test: 已用手机号登录过的设备上,用一个全新三方身份登录,验证只弹一次确认框、不发短信、确认后拿到 token 进 App。

Acceptance Scenarios:

  1. Given 设备存在信任记录(本机验证过手机号),When 用户以未绑定的三方身份发起三方登录且请求携带该设备标识,Then 返回"设备确认"状态,含临时凭证 tempKey 与脱敏手机号(隐藏中间 4 位,如 098****789)
  2. Given 用户在确认框点击"确认",When 提交确认请求(tempKey + 设备标识),Then 三方身份绑定到信任手机号对应的账号(账号不存在则按现有规则自动创建),返回登录 token,响应结构与现有绑定接口一致
  3. Given 用户在确认框点击"取消",When 返回登录页,Then 不产生任何绑定,用户仍可用手机号+短信登录,现有流程完全不受影响

User Story 2 - 设备信任的建立与更新 (Priority: P2)

凡是手机号验证通过的登录,都建立/刷新"这台设备 ↔ 这个手机号"的信任关系:手机号短信登录成功、三方登录走短信绑定成功、三方已绑定身份直接登录成功、一键确认登录成功——四种成功路径都要记录。同一台设备先后登录过不同手机号(如家人共用)时,以最近一次为准覆盖。

Why this priority: 信任记录是 Story 1 的数据基础;没有记录就没有免绑。

Independent Test: 分别走四种登录路径,验证设备信任表被写入;换手机号再登录,验证记录被覆盖为新手机号。

Acceptance Scenarios:

  1. Given 任意用户在设备上完成手机号+短信登录,When 登录成功且请求携带设备标识,Then 设备信任关系被记录(设备标识→手机号→用户)
  2. Given 设备已有信任记录(手机号 A),When 手机号 B 在该设备再次登录成功,Then 记录更新为手机号 B
  3. Given 三方身份已绑定(现有绑定记录存在),When 该用户在设备上三方登录成功,Then 设备信任关系同样被记录(手机号取该绑定用户)

User Story 3 - LINE 网页回调流程同样免绑 (Priority: P3)

LINE 登录走"唤起系统浏览器 → LINE 回调后端 → 302 跳回 App"的链路,此链路后端原本拿不到设备信息。现约定:App 打开 LINE 授权 URL 时把设备标识放入 state 参数带回。回调端对未绑定身份同样按设备信任情况分支:有信任 → 回跳 App 时带"设备确认"参数(tempKey + 脱敏手机号),App 弹同一套确认框;无信任 → 维持现状回跳 needPhone。

Why this priority: LINE 回调是三方登录的一条独立链路,不覆盖会导致 LINE 用户独享不到免绑;但它是增量,不阻塞主流程。

Independent Test: 已信任设备上走 LINE 授权回调,验证 302 回跳参数为 deviceConfirm 分支;新设备走同一回调,验证回跳参数仍为 needPhone。

Acceptance Scenarios:

  1. Given 设备有信任记录,When LINE 回调收到 code 且 state 携带该设备标识、LINE 身份未绑定,Then 302 回跳 App 携带 deviceConfirm 标记、tempKey 与脱敏手机号
  2. Given 设备无信任记录(或 state 未携带设备标识),When 同一回调发生,Then 回跳参数与现状完全一致(needPhone=1&tempKey)
  3. Given LINE 身份已绑定,When 回调发生,Then 直接回跳 token(现状不变),且同时记录设备信任

Edge Cases

  • 老版本 App 不传 deviceId:所有接口行为与现状逐字节一致(needPhone 流程),不报错、不强制。
  • deviceId 传了但设备无信任记录:走原 needPhone 手动绑定流程。
  • 一键确认时 tempKey 过期(有效期与现状一致,5 分钟):确认接口返回明确过期错误,用户重新发起三方登录获取新 tempKey。
  • tempKey 与 deviceId 不匹配(换设备重放/伪造):确认接口拒绝,不产生绑定。
  • 信任手机号对应的账号已被停用或删除:确认接口返回错误提示,不自动绑定;用户回落手动绑定流程。
  • 三方身份其实已有绑定记录:直接签发 token(现状分支优先),不进入设备确认分支。
  • 家人共用设备:信任手机号以最近一次登录为准;一键确认框展示脱敏手机号,用户看得到将绑定给谁、可取消。
  • deviceId 异常值(超长/空串):按未传处理,走原流程。

Requirements (mandatory)

Functional Requirements

  • FR-001: 手机号短信登录、三方登录、三方绑定手机号等登录接口 MUST 接受可选的设备唯一标识参数(字符串,≤64);参数缺省或非法时,接口行为与现状完全一致。
  • FR-002: 凡手机号验证通过的登录成功点(短信登录、三方短信绑定、三方已绑定直登、一键确认登录)MUST 记录或更新设备信任关系(设备标识→手机号→用户ID),同设备以最近一次为准。
  • FR-003: 三方登录遇到未绑定身份时,若请求携带设备标识且该设备存在信任记录,系统 MUST 返回"设备确认"状态:临时凭证 tempKey + 脱敏手机号(隐藏中间 4 位);否则维持现状返回 needPhone + tempKey。
  • FR-004: 系统 MUST 提供免短信设备确认接口:入参为 tempKey + 设备标识;校验通过后按现有绑定规则将三方身份绑定到信任手机号账号(账号不存在则自动创建),签发登录凭证;响应结构与现有"三方绑定手机号"接口一致。
  • FR-005: 确认弹窗展示的手机号 MUST 为只读脱敏展示,用户不可编辑;接口侧不提供"换号"能力,更换手机号走取消后手动绑定。
  • FR-006: LINE 服务端回调 MUST 支持从 state 参数解析设备标识,未绑定分支按设备信任情况返回 deviceConfirm(含 tempKey 与脱敏手机号)或现状 needPhone 回跳;已绑定分支签发 token 并同时记录设备信任。
  • FR-007: 设备确认临时凭证有效期 MUST 与现状一致(5 分钟),过期后确认接口返回明确错误码。
  • FR-008: 确认接口 MUST 校验临时凭证与设备标识的匹配关系,不匹配时拒绝且不产生绑定;信任手机号账号已停用/删除时同样拒绝并给出可读错误。
  • FR-009: 数据库变更 MUST 按《数据库变更管理》规范写入 updatesql/sql.md(标注日期与用途),不直接执行。
  • FR-010: 本功能 MUST 产出一份前端接口契约文档(放 specs/029 下),覆盖:新增/变更入参、oauthLogin 新返回分支、新确认接口、LINE 授权 URL state 约定与回跳分支,交由 uni-app 前端团队实施。
  • FR-011: 设备信任数据 MUST 仅存储必要字段(设备标识、手机号、用户ID、时间戳),不采集其它设备信息;商户端/骑手端登录不在本功能范围。

Key Entities (include if feature involves data)

  • 设备信任记录(新):设备唯一标识(一台设备一条,主键)→ 最近一次验证通过的手机号 → 对应用户ID → 创建/更新时间。生命周期:随每次符合 FR-002 的登录成功 upsert。
  • 三方绑定关系(现有 info_user_oauth,复用不改):三方身份(provider + providerUid)↔ 用户ID。一键确认即向此关系新增记录。
  • 临时凭证(现有 tempKey 机制,扩展):短期(5 分钟)凭据,值从"provider@providerUid"扩展为可携带设备标识,供绑定/确认接口消费。

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 已信任设备上,用户从"点三方登录"到"进入 App"无需短信验证码,操作不超过 2 步(点三方登录 + 点确认)。
  • SC-002: 未携带设备标识、携带但无信任记录、以及旧版本 App 的所有请求,行为与改造前完全一致(回归零差异)。
  • SC-003: 设备确认接口的响应耗时与现有登录/绑定接口处于同一量级,不引入新的明显延迟。
  • SC-004: 前端契约文档交付且覆盖全部涉及接口、返回分支与 LINE 回跳参数,前端团队无需口头追问即可开工。
  • SC-005: 服务层自动化测试覆盖:设备信任记录的建立/覆盖、免绑分支判定、确认接口的成功/过期/不匹配/账号停用路径。

Assumptions

  • 设备唯一标识由 App 端生成并持久化(iOS 建议 Keychain、Android 建议 ANDROID_ID,均跨卸载重装稳定),其稳定性由前端保证;后端只接收并存储字符串(≤64),不关心生成方式。
  • 本功能只改后端;用户端 uni-app 由前端同事按契约文档另行实施(不在本 spec 的交付物内,契约文档在内)。
  • 范围不含商户端(shanglodeing)与骑手端(syslodeing)登录。
  • 沿用 017-oauth-login 的既有决策:手机号始终是账号主键,InfoUser 上不落三方 ID;万能验证码 8888 等现有测试便利不改动。
  • 设备确认弹窗的 UI 文案与样式由前端实现,后端只提供脱敏手机号数据。

2026-10-30 增量:同设备换手机号解除旧第三方绑定

状态:五字段数据库方案已确认,后端已实现;用户已确认测试环境建表 SQL 执行完成,App 联调尚未执行。本节优先于上文涉及“直接覆盖设备信任”或“已有绑定无条件直登”的旧描述。

已确认业务规则

  • 手机号验证码登录,以及第三方流程输入手机号并通过验证码完成绑定登录,均以本次验证成功的手机号为准。后者不要求先单独使用手机号登录。
  • 同设备从 A 切换到 B:删除该设备确认使用过且当前仍属于旧用户 A 的 Apple、Google、LINE 用户端绑定;更新设备信任到 B。第三方绑定流程同时保留本次新建的“第三方身份 → B”绑定。
  • 同号再次登录不解绑。只输入手机号、验证码错误、账号校验失败或登录事务失败均不得改变旧绑定及设备记录。
  • 第三方绑定仍由 (provider, provider_uid) 唯一约束保证一项身份只关联一个用户。设备关联表只记录使用设备,不增加绑定手机号的数量。
  • 删除的是服务器的全局第三方绑定:该身份在其他设备下次也要重新绑定;A 的其他未在本设备使用的绑定保留。删除绑定时清理该绑定在所有设备的关联行。
  • 同一账号新登录使该账号旧 token 失效的现有规则沿用;此规则与设备换号解绑分别处理,不扩大为删除旧账号全部会话。
  • 设备无信任记录且身份未绑定时,输入手机号及验证码完成首次绑定;有信任时可确认当前手机号,也可走完整验证码绑定流程选择其他手机号。一键确认接口本身不接受新手机号。
  • 设备最后一次验证的手机号不能被另一手机号的第三方快捷登录静默覆盖。已有绑定归属不同于设备当前用户时,要求相应手机号验证码登录后重试;不能直接签发旧账号 token。设备无信任记录或归属一致时沿用已绑定快捷登录,并记录使用关联。
  • 用户已决定直接按设备信息处理,不增加设备证明流程。新版本 App 各登录/绑定入口必须提交同一个稳定 deviceId;现有接口对缺失/非法 deviceId 的兼容保留,此类请求无法执行按设备解绑。
  • 上线前旧绑定由开发者自行清理;本轮不推断、不回填历史设备使用关系,不提供自动清空旧绑定逻辑。

新增需求

  • FR-012:新增设备与第三方绑定关联表;用户端第三方成功直登、短信绑定、一键确认及 LINE 回调成功路径均应记录对应绑定 ID,重复记录幂等。
  • FR-013:手机号登录与第三方短信绑定在覆盖设备信任之前检查旧记录,按“设备 ID + 旧用户 ID + 用户端渠道”精确解绑;绑定当前归属必须在事务内重新校验。
  • FR-014:解绑旧绑定、清理关联、建立本次绑定、更新设备记录必须处于同一个数据库事务;失败不得以登录成功响应结束。同设备并发请求应串行处理,已有绑定分支不得绕过手机号验证或签发错误用户 token。
  • FR-015:免短信确认凭证应绑定签发时的设备用户;凭证发出后设备已换手机号的,拒绝旧确认凭证并要求重新发起登录,不能用旧弹窗确认新手机号。
  • FR-016:仅处理普通用户端 Apple、Google、LINE(兼容旧 line 与 line_user),不清理商家/骑手渠道的绑定。

增量验收

  1. D1 使用 Apple X 和 Google Y 登录 A,随后用 B 验证码登录:删除 X→A、Y→A,设备变为 B;A 在其他设备单独使用的 LINE Z 绑定保留。
  2. D1 当前为 A,通过新的 Google Y 验证 B 并绑定登录:先清理 D1 对应旧绑定,最后保留 Y→B,设备变为 B。
  3. 同号再次验证、验证码错误、登录失败、缺少 deviceId:分别验证不误删绑定;缺少 deviceId 不可声称已完成设备解绑。
  4. X→A 在 D1、D2 均使用:D1 换号时删除全局 X→A,并清理 X 的全部设备关联,不留下失效关联。
  5. A 已在 D1 登录,D2 首次验证 A 并绑定:不视为 D1 换号;D1 原 token 因现有同账号登录策略失效,账号资料和绑定不因换设备被删除。
  6. 设备当前为 B,第三方身份仍属于 A:不能直接进入 A;成功验证相应手机号后才可切换。
  7. 并发换号、第三方绑定期间归属变化、旧 deviceConfirm 重放:不出现“设备是 B、返回 A token”或误删本次新绑定。