ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

getPhoneNumber 旧接口停用迁移记:code 换手机号的新写法与三类报错

getPhoneNumber 旧接口停用迁移记:code 换手机号的新写法与三类报错 getPhoneNumber 旧接口停用迁移记code 换手机号的新写法与三类报错适用读者正在维护微信小程序登录、注册、绑定手机号链路的前后端工程师尤其是还在用 encryptedData 解密拿手机号的存量项目维护者。一、老代码是在一个周三早上集体罢工的去年 11 月的一个周三早上九点十几分客户运营群先炸了小程序注册页点「授权手机号」之后一直转圈偶尔弹一个「获取失败」。我们打开后端日志满屏都是 AES 解密返回空、session_key 校验不过的记录。那条链路我们已经跑了两年多一行没改过突然就挂了。旧实现的路径是这样的用户点open-typegetPhoneNumber的按钮回调里拿到e.detail.encryptedData和iv前端再配合wx.login换来的 session_key把密文一起传给服务端服务端用 AES-128-CBC 解出手机号明文。这套写法在 2020 年前后是官方推荐姿势教程满天飞。翻平台公告才发现2023 年 8 月底微信就发了通知手机号获取方式整体升级为「code 换手机号」旧的加密数据解密链路分批停用存量小程序留了数个月缓冲期。**我们就是拖着没迁的那批最后是线上替我们做的决定。**这次迁移前前后后踩了三类报错把过程和结论都记下来给还没动手的同行省点时间。二、新写法button 回调里直接拿 code2.1 前端改动比想象的小WXML 层面几乎零改动按钮还是那个按钮open-type不变。变化全在 JS 回调里——不再碰encryptedData和iv直接取detail.code。!-- 依赖基础库 2.21.2 及以上才能在回调里拿到 detail.code --!-- 按钮写法与旧版完全一致变化全部发生在 JS 回调里 --buttonopen-typegetPhoneNumberbindgetphonenumberonGetPhoneclasslogin-btn授权手机号登录/button// 页面逻辑回调里直接取 detail.code不再碰 encryptedDataPage({onGetPhone(e){// 用户点了「拒绝」时 detail 里没有 code只有 errMsgif(!e.detail.code){// 拒绝授权是正常用户行为不要弹强提示打断他wx.showToast({title:已取消授权,icon:none});return;}// code 有效期约 5 分钟且只能消费一次拿到立刻传后端// 变量先存进局部作用域避免异步过程中被二次取值constcodee.detail.code;wx.request({url:https://api.example.com/auth/phone,method:POST,data:{code},success:(res){// 后端换号成功后返回登录态与脱敏手机号// 这里只写 storage 里的 token手机号串仅用于页面回显wx.setStorageSync(token,res.data.token);wx.showToast({title:登录成功,icon:success});},fail:(){// 网络异常时提示稍后再试不要在这里重发同一个 codewx.showToast({title:网络异常请稍后再试,icon:none});},});},});有两个坑要在前端就堵住。code 的有效期约 5 分钟、只能用一次所以不要把它存进 storage、不要放进重试队列里二次发送。用户点「拒绝」时回调里根本没有 code 字段只看e.detail.code是否存在就能分支。2.2 服务端拿 code 去微信换手机号自建服务端走 HTTP 接口POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_tokenACCESS_TOKEN请求体只有一个{code: ...}返回里的phone_info包含phoneNumber、purePhoneNumber、countryCode和 watermark。用云开发的项目更省事云调用连 access_token 都不用管。// 环境Node.js 18依赖 express 4.x、axios 1.x// access_token 统一收敛在 tokenManager 模块里发放与刷新constexpressrequire(express);constaxiosrequire(axios);constappexpress();// 解析 JSON 请求体前端传的是 { code: xxx }app.use(express.json());// 官方换号接口query 上挂 access_tokenbody 里只放 codeconstPHONE_URLhttps://api.weixin.qq.com/wxa/business/getuserphonenumber;app.post(/auth/phone,async(req,res){constcode(req.body||{}).code;// 入口先挡空值避免拿空 code 去打微信接口白白耗调用次数if(!code){returnres.status(400).json({msg:code 缺失});}try{// token 从统一发号器取内部带提前刷新与并发去重// 千万不要在每个接口里各自调 getAccessToken会互相顶掉consttokenawaittokenManager.getToken();// 微信对 -1 系统繁忙的建议是原样重试这里做最多 3 次退避// 重试期间不要改请求参数-1 只认原样重发for(leti0;i3;i){constrespawaitaxios.post(PHONE_URL,{code},{params:{access_token:token},timeout:5000,});const{errcode,phone_info}resp.data;// 结构先解构出来后面所有分支都基于这两个字段判断// errcode 为 0 表示成功purePhoneNumber 不带区号落库用它// phoneNumber 与 purePhoneNumber 的区别是前者可能带 86 前缀if(errcode0){// 手机号是敏感个人信息前端只给脱敏串明文只留服务端constmaskedphone_info.purePhoneNumber.replace(/(\d{3})\d{4}(\d{4})/,$1****$2);returnres.json({phoneMasked:masked,token:issueToken(masked)});}// 40029 说明 code 过期或已被消费不要重试让前端重新拉授权if(errcode40029){returnres.status(409).json({msg:code 已失效请重新授权});}// 其余错误码先记日志按 200ms 递增等待后再试console.warn(getuserphonenumber fail,errcode,resp.data.errmsg);awaitnewPromise((r)setTimeout(r,200*(i1)));}// 三次都失败就返回通用错误前端提示稍后再试returnres.status(502).json({msg:换号失败请稍后再试});}catch(err){// 网络层异常与业务错误分开记方便区分是微信侧还是自己侧的问题console.error(phone exchange error,err.message);returnres.status(500).json({msg:服务异常});}});云开发侧的等价写法更短适合不想自己维护 access_token 的团队// 依赖云函数环境里的 wx-server-sdkconstcloudrequire(wx-server-sdk);// 初始化时跟随当前云环境避免写死 envIdcloud.init({env:cloud.DYNAMIC_CURRENT_ENV});exports.mainasync(event){// 云调用不需要 access_token平台会用云函数身份代持凭证// 入口同样建议判一次空防止前端把空 code 直接丢进来if(!event.code){// 与 HTTP 版保持一致的错误语义前端好做统一处理thrownewError(code 缺失);}constresawaitcloud.openapi.phonenumber.getPhoneNumber({code:event.code,});// 返回结构与 HTTP 接口的 phone_info 一致可直接取 purePhoneNumberreturnres.phoneInfo;};新旧两条链路放在一起看差别就很直观了新链路 code 换手机号用户点按钮回调直接返回 codecode 传给服务端服务端调 getuserphonenumber微信直返明文手机号旧链路 前端密文加服务端解密用户点按钮wx.login 换 session_key回调返回 encryptedData 与 iv密文传给服务端服务端 AES 解密出手机号新链路里前端彻底退出密码学环节只是个 code 的搬运工。三、三类典型报错与排查路径灰度第一周监控里集中出现过三类 errcode每一类都有明确的触发条件。先给一张对照表再逐个展开。errcodeerrmsg高频原因处置动作40029invalid codecode 过期、重复消费、权限未开通丢弃 code前端重新拉起授权41030invalid page页面路径不在 app.json 里校正 page 参数与 app.json 严格一致-1system busy微信侧抖动原样重试指数退避上限 3 次3.1 40029invalid code这是出现频率最高的一类。复盘下来有三种来源一是灰度期部分用户点完按钮之后等了很久才联网提交code 过了 5 分钟二是前端异常重试逻辑把同一个 code 发了两次第二次必挂三是有一台测试机用的是体验版对应的小程序还没在 mp 后台完成手机号权限的申请调接口直接报 40029。排查顺序建议从「code 是不是只发了一次」查起再看权限配置代码正确性反而是最后才需要怀疑的。3.2 41030invalid page这类报错严格说不是换号接口本身的错而是迁移时顺手加的兜底逻辑带出来的。我们的设计是授权失败后下发一条订阅消息引导用户重试下发时page参数写成了带 query 的完整路径pages/login/index?fromfallback而 app.json 里注册的是pages/login/index路径不一致直接 41030。教训很清楚凡是接口签名里带 page 参数的取值必须能在 app.json 的页面列表里逐字找到query 拆出去另传。3.3 -1系统繁忙与重试策略-1 是微信侧的瞬时抖动官方文档明确建议原样重试。我们一开始偷懒直接透传错误白天高峰期成功率被拉低了一截改成服务端做 3 次指数退避重试200ms、400ms、600ms之后基本抹平。要注意 -1 的响应体里没有phone_info字段重试前先判空别让空指针把服务打挂。3.4 access_token 管理不当的连带问题迁移上线第二天另一条业务线突然报 access_token invalid。追查发现是运维为了「保险」在换号服务所在的机器上也部署了一个 token 定时刷新脚本——两个实例各自调getAccessToken互相把对方的 token 顶失效。**access_token 必须中心化单点生成、统一缓存、提前几分钟刷新或者直接改用官方的 stable_token 接口天然规避互相顶掉的问题。**这也是很多团队迁移新接口时最容易忽视的隐性依赖。方案问题建议各服务自行刷新 token互相顶掉随机性 invalid收敛到统一发号器或 Redis 缓存用 stable_token 接口无官方保证窗口期内返回同一凭证四、原理剖析微信为什么放弃前端解密旧方案的根本问题在于把密码学材料摊在了客户端。session_key 要先通过wx.login换取而它有个出名的脾气只要授权回调之后前端又调了一次wx.loginsession_key 就被刷新旧的那把钥匙解不开新的密文报 -41003。无数教程和踩坑帖都在教人「登录流程里 wx.login 只能调一次」本质上是在给一个脆弱的时序设计打补丁。code 换号把这套时序整个砍掉了。code 的设计目标是它本身不含任何信息只有微信服务端能消费它。前端拿到的 code 即使被截获5 分钟后作废、消费一次即失效泄漏了也无害手机号明文只在微信机房与你的服务端之间传输session_key 这种敏感凭证从头到尾不再需要前端参与。出错率自然也降了——前端解密时代的 -41003、padding error、乱码在新链路里物理上不存在。另外一层是商业与风控机制。新链路绑定了收费的手机号快速验证组件按次计费、每个小程序账号有固定额度的免费体验次数个人主体小程序干脆不开放该组件。批量拉号刷接口的成本被抬上去了平台从机制上抑制了滥用这比单纯加频控有效得多。微信接口业务服务端小程序前端用户微信接口业务服务端小程序前端用户点击手机号授权按钮bindgetphonenumber 回调取 detail.codePOST /auth/phone 携带 codePOST getuserphonenumber 携带 access_token返回 phone_info 与 watermark返回登录态 token 与脱敏手机号进入已登录态从时序图能看出前端与微信之间不再有任何凭证往返信任链的端点收窄到了服务端这正是这次升级的核心意图。五、迁移 checklist灰度、无感与权限我们整个迁移从立项到全量用了两周出头第 1 周双跑灰度第 2 周全量切流。下面这份清单是按实际执行顺序整理的。事项要点灰度方案服务端加开关按 openid 尾号切 10% 流量进新链路双跑一周比对成功率老用户无感已注册用户授权后用 purePhoneNumber 匹配既有账号登录态直接续上UI 不变企业认证与权限个人主体小程序没有手机号快速验证组件接口权限需在 mp 后台提前申请计费确认组件按次计费先核对账号体验额度把营销部门的批量授权场景提前报备监控告警按 errcode 打点40029 占比超过 2% 触发告警重点盯 code 复用类问题回滚开关保留旧解密代码两周出问题可一键切回「老用户无感」这一条最值得多说一句授权按钮交互、页面文案全部保持原样用户感知到的只是「还是点一下就登录了」。手机号匹配账号的逻辑要处理并发注册的边界——两个设备同时授权同一号码靠数据库的号码唯一索引兜底冲突方提示「账号已在其他设备登录」。六、几个容易想当然的误区迁移过程中我们踩过不少「想当然」的坑有些是文档没写透有些是旧思路惯性使然。挑四个最有代表性的展开说每个都附上错误认知、正确做法和后果。误区一以为 code 和旧 encryptedData 一样需要解密。错误认知拿到 code 之后第一反应是「这玩意儿是不是也要拿 session_key 解一下」甚至有人直接套用旧的 AES 解密工具类去处理。正确做法code 不需要解密也解不了。它就是一张兑换券本身不含任何手机号信息只有微信服务端能消费它。前端拿到后原样传给服务端服务端调getuserphonenumber换号即可。后果如果真拿 code 去走解密流程只会得到一堆乱码或报错白白浪费排查时间。我们团队里就有人在这上面耗了半天最后发现 code 压根不是密文。误区二前端把 code 缓存起来复用。错误认知担心用户网络不好把 code 存进全局变量或 storage等「登录成功后再发」甚至放进重试队列里二次发送。正确做法code 的有效期约 5 分钟、只能用一次生命周期应该压缩到「拿到即发出」。前端回调里拿到 code 立刻 POST 给服务端不要存、不要复用、不要放进重试队列。后果第二次消费同一个 code 必报 40029invalid code。我们灰度期有一批用户就是被前端重试逻辑坑的——第一次请求超时后自动重发第二次直接挂掉用户看到的是「获取失败」体验很差。误区三把phone_info原样透传给前端。错误认知服务端换号成功后图省事直接把phone_info整个 JSON 返回给前端让前端自己取phoneNumber展示。正确做法手机号属于敏感个人信息明文应该只落在服务端日志与数据库里。前端只需要脱敏串用于回显比如138****1234登录态用 token 下发即可。后果手机号明文暴露在前端一旦被截获或从页面缓存里翻出来就是一次个人信息泄露事故。合规上也很危险微信对敏感信息外泄的处罚是实打实的。误区四把 -1 系统繁忙当成业务错误直接透传。错误认知服务端收到 -1 就认为「微信挂了」直接把错误返回给前端让用户「稍后再试」。正确做法-1 是微信侧的瞬时抖动官方文档明确建议原样重试。服务端做 3 次指数退避重试200ms、400ms、600ms重试期间不要改请求参数-1 只认原样重发。后果我们一开始偷懒直接透传白天高峰期成功率被拉低了一截用户反复点授权按钮体验和转化都受影响。改成服务端重试后基本抹平这个坑最不值得踩。FAQ读者常见问题Q1个人主体小程序如何申请手机号快速验证组件个人主体小程序目前不开放手机号快速验证组件无法申请。这是微信平台的硬性限制与代码无关。如果业务强依赖手机号只能走企业主体小程序或在个人主体下改用其他身份验证方式如微信登录 手动填写手机号并短信验证。Q2code 过期后用户重新授权是否需要重新登录不需要。code 只是换取手机号的临时凭证与登录态无关。用户重新点一次授权按钮拿到新 code服务端用新 code 换号成功后直接复用原有登录态即可无需让用户重新走一遍完整登录流程。Q3云开发环境下如何监控调用量云开发控制台的「云函数」页面可以查看每个云函数的调用次数、耗时与错误率更细的维度建议在云函数内部自行打点把errcode分布、40029占比等指标上报到日志服务或自定义监控再按 errcode 设置告警阈值。Q4code 换号接口的免费额度用完了怎么办手机号快速验证组件按次计费免费体验额度用完后会开始扣费。建议在 mp 后台提前核对账号额度把批量授权场景如营销活动提前报备避免高峰期额度耗尽导致线上授权失败。往后看小程序的身份类能力都在往「服务端直取」方向收敛手机号只是走得最早的一个。还挂着旧解密链路的项目建议趁早排期别等线上报错那天再动手。迁移中撞到别的报错欢迎在评论区交流错误码和上下文贴全基本都能对上号。参考与延伸手机号快速验证组件官方文档前端接入手机号获取服务端接口文档getuserphonenumberaccess_token 获取与 stable_token 说明微信小程序开发、getPhoneNumber、手机号快速验证、code 换号、phonenumber.getPhoneNumber、access_token、小程序登录user-info/phone-number.html)access_token 获取与 stable_token 说明微信小程序开发、getPhoneNumber、手机号快速验证、code 换号、phonenumber.getPhoneNumber、access_token、小程序登录
返回列表