spec.md 10 KB

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

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 文案与样式由前端实现,后端只提供脱敏手机号数据。