ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

微信小程序获取用户手机号:新接口code换号与原始数据解析

微信小程序获取用户手机号:新接口code换号与原始数据解析 简介这是一份面向微信小程序后端开发者的 PHP 源码资源解决在小程序中获取用户 OpenID 与手机号并完成解密的实际问题。资源共 2 个 PHP 文件压缩包仅 3KB代码精简适合已有一定基础、希望快速接入微信授权流程的开发者。其中 WxBizDataCrypt 类专门处理微信返回的加密手机号数据提供标准的解密入口WechatController 则负责接收前端 code、调用微信官方接口换取 OpenID并串联授权引导、session_key 获取、敏感数据解密等完整环节。通过这份源码可以直观理解 wx.login 与 jscode2session 的配合方式以及 WxBizDataCrypt 在真实项目中的调用规范对搭建用户系统、实现手机号绑定和用户识别很有帮助。目前已有 1180 人学习下载对需要快速落地小程序登录注册功能的后端团队有直接参考价值。 这两年做小程序最常被同行问的一个问题就是怎么正确获取用户手机号。打开搜索引擎一看教程还停留在wx.getPhoneNumber弹窗授权、前端直接拿encryptedData解密那套老流程。真要按照那些老文章去接代码一跑就是报错。原因很简单微信早就把这条链路整个换掉了现在的标准做法是按钮触发 - code 换号 - 后端拿原始数据。这篇就来说清楚当前最稳妥的实现方案以及你费了半天劲拿到手的原文件到底长什么样、怎么解析、又有哪些坑等着你。1. 老接口为什么不能用了手机号获取方案的前后对比先花点时间把背景理清楚。这不是微信故意折腾开发者而是手机号属于最高敏感级别的个人信息平台在隐私合规这件事上的态度一年比一年硬。早期的小程序确实可以靠一个授权弹窗直接拿到用户手机号前端拿到明文存哪儿都行。这种模式对开发者方便但也意味着用户手机号在小程序端裸奔一旦前端代码被反编译或者被人恶意抓包手机号就直接泄露了。于是微信在 2023 年前后开始逐步收紧。第一步是收回了前端直接拿明文的能力改为弹窗后返回encryptedData和iv由后端配合session_key做 AES 解密。这个方案比裸奔强一点但session_key的保管仍然是个隐患。紧接着平台又迈出了第二步连encryptedData和iv都不给了bindgetphonenumber事件里只返回一个临时code后端用这个code去调用微信的服务端接口直接换取手机号明文。新旧方案的对比用一张表就能看明白对比项老方案已不可用当前方案推荐前端拿到什么encryptedData / iv一次性 code解密位置后端用 session_key 解密不需要解密直接调接口换明文调用接口无服务端接口/wxa/business/getuserphonenumber安全级别中间环节多密钥管理风险高换取链路短明文只出现在服务端前置条件企业主体 认证企业主体 认证这套调整的核心逻辑就是把手机号明文彻底从客户端挪走只在服务端落地。所以你在网上搜到的获取手机号的原文件本质上分两层理解从服务端接口拿到的原始 JSON 响应体或者旧方案下解密后的完整数据结构。无论哪一层都意味着手机号的明文数据只能在你的后端服务器里出现。另一个必须要认清的现状是个人主体小程序现在已经无法调用这个能力了。微信的官方文档写得很明白必须是已认证的非个人主体小程序也就是说企业、个体户、政府、媒体这类组织主体才有资格。如果你的小程序还挂在个人名下先把主体升级和认证做完再往下看代码层面的东西。2. 前端只做两件事渲染按钮和把 code 交出去2.1 一个绕不开的 button前端部分其实没有太多技术含量核心就是微信规定获取手机号不能通过 API 直接弹窗只能靠用户主动点击button组件而且这个按钮必须声明open-typegetPhoneNumber。放一个最简单的 WXML 片段button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 微信绑定手机号一键登录 /button注意这里要求的是button组件不是随便拿一个view加个点击事件就可以的。微信的这类敏感授权接口都默认走用户主动触发原则你必须给用户一个明确的按钮用户点了平台才会下发code。我之前见过有人把open-type挂在自定义组件上结果死活不触发排查了半天才发现微信文档里写明只支持button原生组件改回去就好了。2.2 回调里只有一个 code再看回调函数这是前后端交接的最后一个环节Page({ async onGetPhoneNumber(res) { const { errMsg, code } res.detail; if (errMsg ! getPhoneNumber:ok || !code) { // 用户点了取消按钮或者平台下发 code 失败 wx.showToast({ title: 需要手机号才能继续, icon: none }); return; } // 把 code 交给后端让后端去换手机号 const resp await wx.request({ url: https://your-api.com/api/login-with-phone, method: POST, data: { code }, }); // 处理登录状态... }, });最关键的一点来了res.detail里面现在只有code没有encryptedData和iv。很多从老项目迁移过来的开发者容易在这里卡住还按老一套把encryptedData传给后端解密结果拿到手就是个 undefined然后开始怀疑人生。这就是平台规则变更带来的惯性坑。code有很短的时效性官方说法是 5 分钟内有效而且只能用一次。用完之后你再提交同一个code服务端接口会直接报错。所以前端拿到code后应该第一时间传给后端不要在本地做任何持久化也不要把它打进日志里。2.3 模拟器和真机的差异还有一个小细节微信开发者工具的模拟器里可以手动模拟点击授权但拿到的code是假的后端换号必然失败。所以测试手机号获取功能时一定要在真机上跑而且建议用不同的微信号分别测试已绑定手机号和未绑定手机号两种状态。未绑定手机号的用户点击按钮后通常会走平台兜底的验证流程返回的code类型也可能不一样这点后面说坑的时候再展开。3. 后端那一步用 code 换回接口返回的原始数据3.1 先拿到 access_token后端任务分两步先拿access_token再拿手机号。access_token是调用微信服务端接口的通行证有效期 7200 秒需要通过小程序的 AppID 和 AppSecret 换取const tokenCache { token: , expireAt: 0 }; async function getAccessToken() { if (tokenCache.token Date.now() tokenCache.expireAt) { return tokenCache.token; } const url https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credential appid${APPID}secret${APPSECRET}; const res await fetch(url).then((r) r.json()); if (!res.access_token) { throw new Error(获取 access_token 失败: ${JSON.stringify(res)}); } tokenCache.token res.access_token; // 提前 300 秒过期避免恰好碰到 token 过期临界点 tokenCache.expireAt Date.now() (res.expires_in - 300) * 1000; return res.access_token; }两个容易踩的坑一是AppSecret 不能出现在前端代码里只能在服务端环境变量中引用二是access_token必须做缓存不要每个请求都去调 token 接口微信对 token 接口的调用频率是有限制的频繁调用会被临时封禁。我见过有同事在循环里反复换取 token结果接口直接返回 45009 报错。3.2 调用 getuserphonenumber 接口拿到access_token之后真正换取手机号的接口就一条async function getPhoneNumber(code) { const accessToken await getAccessToken(); const res await fetch( https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token${accessToken}, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ code }), } ).then((r) r.json()); if (res.errcode ! 0) { throw new Error(换取手机号失败: ${JSON.stringify(res)}); } return res.phone_info; }注意请求方式是 POST参数体就是一个JSON字符串{code: 前端传来的code}。接口返回的errcode为 0 表示成功非 0 时需要把完整的返回体记录到日志里方便排查。3.3 原文件到底长什么样这是很多人真正关心的问题。成功返回时微信给的原始数据长这样{ errcode: 0, errmsg: ok, phone_info: { phoneNumber: 13580006666, purePhoneNumber: 13580006666, countryCode: 86, watermark: { appid: wx1234567890abcdef, timestamp: 1700000000 } } }这里每个字段都有用别只盯着phoneNumberphoneNumber带国家区号的完整手机号一般情况下和purePhoneNumber一致如果是海外手机号前面会带区号之类的格式。purePhoneNumber不带区号的纯手机号国内场景下通常就是 11 位号码存库推荐存这个。countryCode国家代码中国是86。watermark.appid这次数据的归属小程序可以用来校验返回数据是否真的来自你的小程序。watermark.timestamp数据生成时间戳可以当作取号时间的参考但不能完全依赖它做业务时序判断。这个phone_info对象就是标题里说的获取手机号的原文件的最终形态。整个流程走完之后手机号明文只出现在这一步的后端响应里前端从头到尾没见过手机号。这也是现在合规审计最认可的一种数据流形态。3.4 老方案解密还需要学吗现实情况是你接手一个老项目时可能会遇到还依赖encryptedData解密的老代码。老方案的核心是用wx.login换session_key再用session_keyencryptedDataiv做 AES 解密。但 2023 年底之后微信已经逐步停用旧的加密授权链路逐步切换到code换取模式。新项目建议直接走新接口老项目如果接口还能用就维持现状但要做容量评估尽早迁移到上面这套流程。另外要注意getuserphonenumber接口是不区分手机号是否绑定微信的。用户点击按钮后如果微信检测到用户没有绑定手机号会转到一个快速验证流程用户需要手动输入一个手机号并完成验证。这种情况下返回的code同样可以换到phone_info但语义上属于人工验证通过的手机号不是微信账户默认绑定的手机号。业务上要不要区分这两种来源需要产品层面提前约定好。4. 调试、抓包与高频踩坑让手机号数据真正落库4.1 怎么确认自己拿到的响应是对的前面讲了那么多最后还是要落到调试上。最常见的问题就是前端把code传过来了后端也调了接口但拿到的返回结构和自己预期对不上。这时候我最常用的排查方式第一直接在服务端代码里把res完整打出来看errcode和errmsg的具体内容。不要只打errMsg很多关键信息在errmsg字段里比如invalid code、code been used、access_token expired一眼就能看出问题方向。第二用微信开发者工具的 Network 面板看小程序前端的请求确认请求确实发出去了、URL 和参数格式是不是自己以为的那样。往往排查到最后会发现参数名打错了或者code传成了空字符串。第三如果涉及到真机调试可以在wx.request的complete回调里临时加一行console.log(res)然后用 vConsole 在真机上直接看返回。这一步虽然土但很管用能排除掉大量我以为传了但其实没传的问题。稍微提一句抓包工具。真要深入排查前后端数据流可以配合 Charles 这类工具看 HTTPS 报文前提是你得在本地安装并信任对应的根证书而且我建议只用来调试自己开发调试环境下的小程序别拿去做任何绕过平台限制的事情。多数情况下开发者工具的 Network 面板加日志打印已经足够解决问题了。4.2 我在这条链路上踩过的具体坑我把自己做这块开发时遇到频率最高的几个问题整理成了一张表每个都是真实发生过的现象根本原因解决办法接口返回 40001access_token 无效或过期检查 token 缓存逻辑确认 appid/secret 是否正确接口返回 40003openid 相关参数错误确认是不是用了错误的 code或 code 已过期接口返回 code been used同一个 code 被重复提交前端做提交防抖后端对 code 做幂等处理按钮点击毫无反应open-type 没写在原生 button 上改成原生 button 组件并确认基础库版本返回结构没有 phone_info接口调用失败只返回了 errcode/errmsg打印完整返回体对照文档排查个人主体无法调用主体类型不符合要求升级为企业主体并完成微信认证用户手机号未绑定微信平台走快速验证流程返回的 code 语义不同后端在返回手机号时记录一个获取来源字段这里特别想说一下code 重复提交这个问题。前端在某些场景下因为网络波动会重试同一个请求后端如果没有做幂等处理第二个请求带着同一个code打过来微信接口直接报错。我现在的做法是领取到code的瞬间先写到 Redis设置 10 分钟过期同一个code只允许成功换取一次第二次直接抛业务异常避免无意义的微信接口调用。还有一个小细节容易被忽视手机号接口是有频率限制的。同一个用户短时间内反复触发取号操作或者测试时用一个微信号疯狂点击按钮很容易触发微信侧的风控接口会返回操作频繁之类的错误。最直接的影响就是用户端体验变差——明明点了按钮却拿不到手机号。生产环境最好在业务层做限制比如同一个用户 60 秒内只能点击一次。4.3 运营合规别让违规处罚影响取号能力写到最后必须多嘴一句获取手机号是平台高度重视的权限如果你的小程序因为诱导分享、虚假宣传、违规支付等手段被平台处罚轻则部分能力被收回重则手机号接口直接不可用。现实中已经有不少团队吃过这个亏——业务正常运营着突然发现手机号登录功能失效后台一查账号被标记违规支付功能和开放能力同时被暂停。手机号获取能力本质上是个特权接口你的小程序整体健康度越高这个接口就越稳定。所以上线前把隐私政策、用户授权协议都补齐不要想着走灰色路径拿号合规才是长期稳定运行的前提。最后分享一个小经验整个流程跑通之后建议你把手机号获取和用户登录注册的链路合并设计不要把取号当成一个孤立的接口。用户的手机号拿到后第一时间做格式校验国内手机号是 11 位纯数字以 1 开头然后查重、落库、绑定 openid最后再下发业务登录态。手机号本身是敏感数据落库时必须加密存储日志里不要打明文。另外微信返回的watermark.timestamp虽然是平台生成的时间但最好还是以你自己服务端的业务时间为准避免多个环境时钟不一致给后面的数据审计带来麻烦。这套链路我前前后后接了不少项目真机调试、后端排查、合规整改都走过一遍按照上面的顺序来能少走很多弯路。本文还有配套的精品资源点击获取
返回列表