2026-09-18-miniprogram-phone-quick-login-design.md 8.7 KB

山峡小程序「手机号快捷登录」设计

日期:2026-09-18
适用范围:miniprogram 登录页 + backend 微信换票 / 绑号
参考实现:/Users/zlwu/work/00code/zkxw(mobile-h5 + backend mini auth)
视觉:登录页协议/隐私下方新增按钮,样式对齐 zkxw「手机号快捷登录」


1. 目标与已确认决策

1.1 目标

  1. 登录页在协议、隐私下方增加「手机号快捷登录」按钮;点击后可完成登录并进入工作台。
  2. 账号密码登录保持现有行为,与快捷登录互不影响。
  3. 快捷登录用微信手机号匹配已有 sys_user.phone_no;唯一命中则登录并绑定 wx_openid;未找到 / 多账号失败(不自动注册)。

1.2 决策摘要

项 约定
身份策略 对齐 zkxw:phone_no 匹配 + 绑定 wx_openid
自动注册 不做
密码登录后静默绑微信 不做(仅快捷登录路径绑号)
仅 openid 静默换票接口 不做(本期不做 wechat-bind / silent login)
密钥配置 新增 WECHAT_MP_APPID + WECHAT_MP_SECRET;WXPAY_* 不动
前端交互 协议下方按钮;open-type="getPhoneNumber" + 同次 uni.login;未勾选协议时确认键同次拉授权
JWT 与密码登录同一套 access / refresh / userInfo

1.3 非目标

  • 密码登录成功后静默绑定 openid
  • 仅 openid 静默登录 / OA 深链换票
  • 自动注册新用户
  • web-admin 微信登录
  • 改微信支付 / 模板消息现有 WXPAY_* 配置用途
  • 客户端 / 供应商 / 配送员多端分 App 登录

2. 端到端流程

登录页「手机号快捷登录」
  →(未勾选协议)确认弹窗「同意并登录」确认键 open-type=getPhoneNumber
  →(已勾选)button open-type=getPhoneNumber
  → e.detail.code → phoneCode
  → uni.login → loginCode
  → POST /api/mini/auth/wechat-login { loginCode, phoneCode }
  → 后端 jscode2session → openid
  → 后端 getuserphonenumber → 规范化手机号
  → 按 phone_no 唯一匹配 sys_user(status 正常、del_flag=0)
  → 首次写 wx_openid;已绑则校验一致
  → 签发 JWT(同密码登录)
  → 前端存会话 → 拉 menu-codes → 进工作台

密码登录:现有 POST /api/web/auth/login(RSA)不变;不调用微信绑号。


3. 配置

3.1 后端环境变量(新增)

环境变量 用途 说明
WECHAT_MP_APPID 小程序 AppID 与门户正式号一致:wx7db1ddb2a90c9425
WECHAT_MP_SECRET 小程序 AppSecret 仅后端;禁止写入前端源码

WXPAY_MINI_APPID / WXPAY_SECRET 保持不动(支付/模板消息)。

另需:

项 说明
API_ENCRYPT_EXCLUDE_PATHS 加入 /api/mini/auth/wechat-login
前端 manifest.json → mp-weixin.appid 已是 wx7db1ddb2a90c9425,须与 WECHAT_MP_APPID 一致

实现完成后由人工将真实 WECHAT_MP_SECRET 填入 backend/.env / .env.production。

3.2 微信公众平台前置条件

要求 说明
主体 非个人主体,且已完成微信认证
能力 开通「手机号快速验证」(可能按次计费)
合法域名 request 域名含现有 API 主机(如 w.shanxiachem.com)
基础库 使用新版 e.detail.code(约 ≥ 2.21.2);不用旧版 encryptedData 解密
验收 以真机为准(开发者工具对 getPhoneNumber 不可靠)

官方参考:


4. 数据模型

4.1 sys_user 增列

列名 类型 说明
wx_openid VARCHAR(64) NULL 本小程序下 openid;未绑定为 NULL
wx_unionid VARCHAR(64) NULL 有则写入;本期不做多端打通依赖

约束:

  • 唯一索引建议:uk_sys_user_wx_openid(wx_openid);MySQL 5.7 下多 NULL 可共存;应用层保证同一 openid 不绑两人。
  • 匹配手机号:phone_no trim / 去 +86/86 前缀规范化;要求 status=1、del_flag=0;发 token 前角色校验与密码登录一致。
  • 同一规范化手机号命中多条启用账号:明确错误,不自动选人。

SQL 落在 docs/sql/ 增量脚本,并同步 ORM SysUser。


5. 后端 API

5.1 POST /api/mini/auth/wechat-login

  • 鉴权:无需 Bearer(登录入口)
  • 请求体:
{
  "loginCode": "<uni.login 返回的 code>",
  "phoneCode": "<getPhoneNumber 返回的 code>"
}

本期登录页场景 始终传 phoneCode;后端无 phoneCode 时返回明确错误(不做 openid-only 静默登录)。

  • 成功 data(与密码登录对齐):
{
  "token": "<access JWT>",
  "refreshToken": "<refresh JWT>",
  "expiresIn": <秒>,
  "userInfo": { }
}

5.2 业务分支

情况 行为
openid 已绑定用户 必须与本次授权手机号对应同一用户(phones_match);不一致 → 失败
未绑定 按规范化手机号查启用用户
0 条 失败:未找到员工账号
>1 条 失败:手机号对应多个账号
唯一命中 写入 wx_openid(及可选 wx_unionid)并 commit → 发 JWT
openid 已被其他用户占用 冲突失败

5.3 微信 HTTP API(自建后端,非云开发)

  1. GET https://api.weixin.qq.com/sns/jscode2session(WECHAT_MP_APPID + WECHAT_MP_SECRET + loginCode)
  2. GET https://api.weixin.qq.com/cgi-bin/token → access_token
  3. POST https://api.weixin.qq.com/wxa/business/getuserphonenumber(body: { "code": phoneCode })

5.4 失败提示(前端 Toast / 接口 message)

场景 提示方向
用户拒绝授权 / 无 code 需要授权手机号,或请使用账号密码登录
未找到匹配员工 未找到对应员工账号(请确认手机号已在系统维护)
同一手机号多账号 手机号对应多个账号,请联系管理员
openid 已绑他人 / 与手机号不一致 微信与账号不匹配,请联系管理员
账号停用 / 无角色 与密码登录相同的拒绝原因

6. 前端改动

位置 改动
miniprogram/pages/login/login.vue 协议/隐私下方:「或」分隔 +「手机号快捷登录」;样式对齐 zkxw;协议门禁
协议确认弹窗 快捷登录入口打开时,确认键带 open-type="getPhoneNumber",同次点击完成同意+授权
miniprogram/config/api.js 增加 wechat-login 路径常量
miniprogram/stores/user.js(或等价 auth 工具) wechatLogin({ loginCode, phoneCode });成功后复用现有存 token、menu-codes、进工作台

密码登录按钮与表单逻辑保持不变。


7. 后端改动清单

位置 改动
app/api/mini_auth.py(新) wechat-login 路由
app/services/wechat_mini_auth.py(新) code2session、get_phone_number、手机号规范化
app/services/wechat_login_service.py(新) 匹配 / 绑定 / 冲突校验
现有发 JWT 能力 复用密码登录签发(如 issue_tokens / public 登录同路径逻辑)
app/models/models.py SysUser.wx_openid / wx_unionid
app/crud/user.py get_by_wx_openid、list_active_by_phone_no 等
app/core/config.py WECHAT_MP_* + 加密排除
app/api/__init__.py + app/main.py 注册路由
docs/sql/ 增量 DDL

实现时可对照移植 zkxw 对应文件,按山峡现有命名与 JWT 签发方式适配,不引入密码登录后的 wechat-bind。


8. 验收标准

  1. 账号密码登录仍可用,行为与现网一致。
  2. 已勾选协议时,点「手机号快捷登录」→ 授权 → 匹配成功 → 进入工作台。
  3. 未勾选协议时,确认「同意并登录」可同次拉起手机号授权并完成登录。
  4. phone_no 为空 / 不匹配 / 多账号 → 失败且不创建用户。
  5. 首次成功写入 wx_openid;再次快捷登录同号同微信可进。
  6. openid 与手机号对应关系冲突时拒绝登录。
  7. 真机验证通过(开发者工具仅作联调参考)。

9. 风险与依赖

  • Secret 未配置:接口应返回可理解错误,避免空串调微信。
  • AppID 不一致:manifest / WECHAT_MP_APPID / 微信后台必须同一正式号。
  • 手机号未维护:管理员需保证员工 sys_user.phone_no 正确且唯一。
  • 计费:手机号快速验证可能按次收费,需在公众平台关注额度。