2026-10-21 波波。写这份的起因:义父问「微信登录不是自动获取微信号和绑定手机吗?」
一路查下来发现两件事——① 登录那次只能拿到 openid/unionid,拿不到手机号(微信限制,不是我们写漏);
② 真正漏的是 unionid(1043 个用户该列全是空)。
记下来是为了以后不再有人(包括我)去赌「点一下聊天就静默拿到手机号」。
| 途径 | 能拿到 | 前提 | 代价 |
|---|---|---|---|
jscode2session(登录) | openid、session_key、unionid(若绑开放平台) | code 一次性 | 免费、用户无感 |
getPhoneNumber | 真实手机号 | 用户主动点击授权 + 企业主体 + 小程序已认证 | 按次计费 0.03 元 |
getUserInfo / getUserProfile | 2022-02 起已回收 | — | |
| 客服会话 | 什么都不给(会话内容归客服系统) | — | — |
结论:
对同一个微信用户给同一个 unionid ⇒ 能免费把「看慢直播的人」和「进小程序的人」认成同一个人。
前提:小程序必须已绑定微信开放平台(未绑定 ⇒ 返回为空,登录不受影响)。
<button> 的 open-type 只能有一个值 ⇒ getPhoneNumber 与 contact 互斥,不存在「一个按钮既进客服会话又拿到手机号」。
open-type="contact" 由微信立即拉起客服会话,前端拦不住(catchtap 也拦不住),所以「聊天之前先弹授权」这条路是死的。
js_code 是一次性凭证 ⇒ unionid 必须在同一次 code2Session 里取回,不能事后「再调一次补上」(第二次必失败)。
| 挂点 | 位置 | 状态 |
|---|---|---|
| 下单联系人表单 | components/foundation/f-contact-form(一处改,item-detail / product-detail / hotel-booking 三页全生效) | ✅ 已绑过的号自动回填;未绑给「用微信手机号填入」 |
| 联系条常驻入口 | components/foundation/f-concierge-bar(bar + inline 两种形态) | ✅ 未绑号时显示「留个号,管家回你」 |
| 商品详情咨询旁 | pages/product-detail | ✅ |
| 「我的」页 | pages/profile | ✅(既有) |
| 全部客服(聊天)入口 | utils/phoneAuth.promptAfterChat() | ✅ 15/18 个 contact 按钮已挂(见下) |
聊天入口的做法(因为约束 2,只能这样):在 bindtap(必先于 open-type 行为触发,时序确定)
里把授权浮层挂上、延时 600 ms 弹出 ⇒ 用户关掉客服会话回到页面时浮层正等着他。
刻意不用 bindcontact:它在不同微信版本触发时机不一致,赌它等于埋一个静默失效的坑。
冷却 7 天一次;已绑号 / 页面没挂浮层 ⇒ 静默放过。
覆盖账(全站 18 个 open-type="contact" 按钮 / 10 个 wxml):
hotel-booking、local×6(含 3 个 catchtap 的 onAskStop)、product-detail
backend/app/routers/auth.py:_resolve_openid → _resolve_wechat_identity,返回 (openid, unionid);
新用户 INSERT 带上;存量用户只补空值、绝不覆盖(下次打开小程序自动补上)。
回归测试 backend/tests/test_login_unionid.py(7 条)。
goods_expose 目前只有 5 条,page_enter 99 条)⇒ 补上才算得出「曝光→详情→下单→支付」漏斗analytics_events.event_name 白名单 + 长度上限(库里混进了约 10 条 SQL 注入探测 payload)