微信小程序获取头像昵称与手机号:最新方案与避坑指南

发布时间:2026/10/1 13:12:47
微信小程序获取头像昵称与手机号:最新方案与避坑指南 1. 聊聊现阶段的微信授权头像昵称和手机号到底怎么拿小程序开发里被问得最多的需求之一就是获取用户的微信基础信息头像、昵称和手机号。以前很简单直接 wx.getUserInfo 弹窗让用户授权再 getPhoneNumber 拿加密数据交给后端解密一套流程下来就完事。但现在这套老方案基本走不通了——微信在 2022 年前后调整了授权策略wx.getUserInfo 拿不到真实头像昵称老版 getPhoneNumber 返回的加密数据也逐步被新方案取代。这篇我把整套链路从头讲到尾前端页面怎么写、后端接口怎么接、登录态怎么处理、真机调试和上架审核有哪些坑一次性说清楚。以 Java Spring Boot 为后端示例其他语言思路完全一致只是 HTTP 请求和 JSON 解析的写法不同。项目技术栈常见的 Spring Boot 小程序原生、若依前后端分离、FastAPI Vue 小程序只要把接口对接好原理都是同一套。先给一个核心结论现在的微信小程序里能稳定拿到的东西只有三样——头像临时路径、昵称用户填写或从微信填充、手机号通过 button 触发后拿 code 去后端换。而 openid、unionid、session_key 这些属于后端通过 code 换取的会话信息前端拿不到也不该拿。理解了这条边界后面的开发就顺了。2. 前端第一关头像与昵称采集不再是弹窗授权2.1 旧方案为什么废了老写法是这样的// 老代码现在基本拿不到真实数据 wx.getUserProfile({ desc: 用于完善会员资料, success: (res) { this.setData({ avatarUrl: res.userInfo.avatarUrl, nickname: res.userInfo.nickName }) } })在 2022 年 10 月之后基础库 2.21.2 以上的环境里这段代码要么直接不弹窗要么返回的是「微信用户」这种匿名昵称和一张灰色默认头像。原因是微信不再允许开发者通过授权弹窗直接读取用户的真实身份信息改成了由用户主动填写或在特定组件里选择的交互模式。这背后的产品逻辑很好理解以前那种「不授权就不能用」的强制授权方式本质上是在用功能换用户隐私用户体验很差也容易滋生灰产。所以微信把决策权交回给用户——头像你自己选昵称你自己填或者一键带入微信昵称手机号你必须明确点一下按钮。2.2 新版头像昵称填写的标准写法替代方案是「头像昵称填写能力」核心就两个组件。头像使用带open-typechooseAvatar的 buttonbutton classavatar-btn open-typechooseAvatar bind:chooseavataronChooseAvatar image src{{avatarUrl}} modeaspectFill classavatar-img / text classavatar-tip点击选择头像/text /button昵称使用typenickname的 inputinput typenickname classnickname-input placeholder请输入昵称 bind:bluronNicknameInput value{{nickname}} /对应的 JS 逻辑Page({ data: { avatarUrl: , nickname: }, onChooseAvatar(e) { // e.detail.avatarUrl 是微信返回的临时文件路径 this.setData({ avatarUrl: e.detail.avatarUrl }) }, onNicknameInput(e) { this.setData({ nickname: e.detail.value }) } })这里有个非常容易踩的坑e.detail.avatarUrl是一个临时路径格式是wxfile://tmp_xxx不是永久有效的。如果你直接把这个字符串存到后端数据库用户下次打开小程序时会发现头像裂了。正确做法是前端拿到临时路径后先把图片上传到自己的对象存储腾讯云 COS、阿里云 OSS 或者自建服务器都行拿到一个永久 URL 再提交给后端。我见过不止一个新手项目在这里翻车——本地开发环境一切正常因为开发者工具里临时文件还在但线上跑两天后用户头像集体失效排查了半天才发现是路径过期了。2.3 typenickname 的一个交互细节typenickname的 input 在微信原生层做了特殊处理用户点击输入框时键盘上方会出现一个「使用微信昵称」的快速填充按钮用户点一下就能自动填入微信昵称。这是一个很顺滑的交互但注意它不会自动把昵称同步到你的 data 里所以bind:blur是必须的否则用户点了快速填充后直接点保存按钮你拿到的还是空字符串。还有一点经验bind:blur的触发时机是输入框失焦如果用户在输入框有焦点时直接点保存部分 Android 机上 blur 可能不触发。稳妥一点的做法是保存时同时读取 input 的 value或者在bind:confirm键盘确认键里也做一次赋值。// 兼容 blur 和 confirm 两种场景 onNicknameInput(e) { const value e.detail.value this.setData({ nickname: value }) }2.4 头像上传的选型建议头像上传我建议直接用云开发或对象存储的直传方案而不是让小程序把图片文件传到自己的后端再转存。原因很简单小程序上传文件的并发和带宽有限如果所有头像都走后端转发服务器压力大不算传输速度也会明显变慢用户换头像时等待时间长。以腾讯云 COS 为例小程序端用wx.uploadFile直接上传到 COS需要先通过后端接口获取临时密钥然后前端用 SDK 直传。核心步骤是后端提供一个接口返回 COS 临时密钥和预计上传路径。前端拿到密钥后用cos-wx-sdk-v5上传图片。上传完成后把返回的 CDN 地址提交给后端保存。如果项目不想引入云服务那就在自己的服务器上用wx.uploadFile接收文件后存到本地或 OSS这个方案技术门槛低但后面要做图片压缩、路径校验和静态资源服务工作量会多一些。3. 手机号获取从 button 触发到 code 换手机号3.1 先搞清楚现在用的是哪套方案手机号获取是这次改造中变化最大的部分。现在主流的方案是手机号快速验证组件核心流程是前端放一个open-typegetPhoneNumber的 button。用户点击后微信弹窗让用户确认是否允许小程序获取手机号。用户同意后bind:getphonenumber回调的e.detail里返回一个code这个 code 有效期为 5 分钟且只能使用一次。前端把 code 交给后端。后端拿着 code 调用微信的getuserphonenumber接口换取用户的完整手机号。老的方案是e.detail.encryptedDatae.detail.iv后端用 session_key 做 AES 解密拿到手机号。在基础库 2.21.2 之前创建的小程序还能用老方案新项目直接用新方案就好不用走回头路。注意区分一件事这里的 code 和 wx.login 的 code 是两个东西手机号 button 返回的 code 只能用来换手机号wx.login 返回的 code 只能用来换 openid 和 session_key不能混用。我见过有人在登录接口里把手机号的 code 传过去结果后端拿着它去调 jscode2session微信返回invalid code排查了半小时才发现是传错参数了。3.2 前端完整代码WXML 里的按钮button classphone-btn open-typegetPhoneNumber bind:getphonenumberonGetPhoneNumber loading{{phoneLoading}} 获取手机号 /buttonJS 逻辑Page({ data: { phoneLoading: false }, async onGetPhoneNumber(e) { // 用户拒绝授权时errMsg 不是 ok if (e.detail.errMsg ! getPhoneNumber:ok) { wx.showToast({ title: 需要授权才能获取手机号, icon: none }) return } const code e.detail.code if (!code) { wx.showToast({ title: 获取失败请重试, icon: none }) return } this.setData({ phoneLoading: true }) try { const res await new Promise((resolve, reject) { wx.request({ url: https://your-api.com/miniapp/phone, method: POST, data: { code }, success: resolve, fail: reject }) }) const data res.data if (data.code 0) { this.setData({ phone: data.data.phone }) wx.showToast({ title: 手机号获取成功, icon: success }) } else { wx.showToast({ title: data.msg || 获取失败, icon: none }) } } catch (err) { wx.showToast({ title: 网络异常请重试, icon: none }) } finally { this.setData({ phoneLoading: false }) } } })这段代码里有个细节值得展开e.detail.errMsg的判断。用户取消授权时errMsg的值是getPhoneNumber:fail user deny不同基础库版本可能略有差异如果你不判断这个值就继续往下走e.detail.code是 undefined后端会收到一个空 code白白浪费一次网络请求。另一个细节是按钮的loading状态。手机号授权弹窗关闭后用户会回到页面这时候如果你马上发起 request用户还能看到按钮的加载状态但如果用户点了授权后迟迟不关弹窗loading 就一直不触发。实际项目里我习惯在onGetPhoneNumber被触发时就立刻把 loading 置为 true不管用户同没同意等回调结束或接口返回后再解除这样能防止用户连点两次按钮导致 code 重复使用。3.3 必须知道的前置条件与频控限制手机号快速验证组件有几个硬性限制开发前就要确认清楚主体限制个人主体的小程序无法使用手机号快速验证组件。只有企业、个体工商户、事业单位等非个人主体才能开通。如果你的项目是个人开发者接的单但这个「小程序」是挂在个人主体下的手机号能力直接不能用只能引导用户手动输入手机号并通过短信验证码校验。频控限制同一个手机号在同一个小程序里30 天内能获取的次数是有限的官方没有把具体数字写死在文档里但实际测试时你会发现超过一定次数后接口会报错误码。所以生产环境千万不要拿用户的手机号反复测试调试时尽量用开发者工具的模拟手机号。用户选择的手机号不一定等于微信绑定手机号新版组件弹窗里有一个「使用其他手机号」的选项用户可以选择一个和微信绑定手机号不同的号码授权给你。所以后端拿到手机号后不要假设它一定是用户微信的绑定号码也不要拿它去反查用户的微信身份它在业务上只是「用户授权给你的一个手机号」。3.4 开发者工具里的手机号模拟微信开发者工具在「模拟操作」面板里提供了手机号授权模拟功能可以输入任意手机号来模拟授权结果。这样一来前端联调时不依赖真机也能走通整个流程。但注意模拟环境返回的 code 是假的后端拿着它调用 getuserphonenumber微信接口会返回一个固定测试手机号通常是模拟器里填的那个号码但线上不会真的返回你填的号码。所以如果你要验证后端换手机号的完整链路最好用真机预览调试或者先把后端逻辑跑通等到真机阶段再整体验证。4. 后端重头戏登录态、access_token 与手机号解析4.1 登录接口用 wx.login 的 code 换 openid前端在 App 启动或者进入需要登录的页面时会调用wx.login获取一个临时 code然后传给后端。后端拿这个 code 调微信的jscode2session接口换取 openid 和 session_key。这里用 Spring Boot Hutool 的写法演示RestController RequestMapping(/miniapp) public class MiniappLoginController { Value(${wx.appid}) private String appid; Value(${wx.secret}) private String secret; PostMapping(/login) public Result login(RequestBody LoginRequest request) { String url String.format( https://api.weixin.qq.com/sns/jscode2session?appid%ssecret%sjs_code%sgrant_typeauthorization_code, appid, secret, request.getCode() ); String resp HttpUtil.get(url); JSONObject json JSONUtil.parseObj(resp); if (json.containsKey(errcode)) { return Result.error(登录失败 json.getInt(errcode)); } String openid json.getStr(openid); String sessionKey json.getStr(session_key); String unionid json.getStr(unionid); // 可能为空 // 查询或创建用户返回自定义 token User user userService.findOrCreateByOpenid(openid, unionid); String token userService.createToken(user.getId()); return Result.success(ImmutableMap.of( token, token, hasPhone, StringUtils.isNotBlank(user.getPhone()) )); } }注意unionid只有在小程序绑定了微信开放平台账号且用户关注了同主体下的公众号或 App 时才会返回。绝大多数单小程序项目用不到 unionid直接用 openid 作为用户唯一标识就够了。session_key需要后端缓存下来Redis 或本地 Map 都行有效期和 session_key 的过期策略对齐。新版手机号获取虽然不直接用 session_key 解密但后续如果要做微信运动、内容安全检测这些接口还是需要 session_key。4.2 access_token手机号解密接口的前置依赖新版手机号换取接口getuserphonenumber需要access_token作为鉴权参数而这个 access_token 不是用户身份 token而是小程序的全局调用凭证。获取 access_token 的地址https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidAPPIDsecretSECRET返回结果是{ access_token: xxx, expires_in: 7200 }access_token 的有效期是 2 小时而且微信对获取次数有限制每日调用额度有限。所以必须做全局缓存不能每次请求都去拉新的。用 Spring Boot 实现一个简单的缓存Component public class WxAccessTokenManager { Value(${wx.appid}) private String appid; Value(${wx.secret}) private String secret; private String accessToken; private long expireTime; public synchronized String getAccessToken() { // 提前 5 分钟过期留出缓冲 if (System.currentTimeMillis() expireTime - 5 * 60 * 1000) { return accessToken; } String url String.format( https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid%ssecret%s, appid, secret ); String resp HttpUtil.get(url); JSONObject json JSONUtil.parseObj(resp); if (json.containsKey(access_token)) { this.accessToken json.getStr(access_token); this.expireTime System.currentTimeMillis() json.getLong(expires_in) * 1000; return accessToken; } throw new RuntimeException(获取 access_token 失败 resp); } }这里有个很坑的细节access_token 在并发场景下必须用全局锁或分布式锁保证只有一个线程去刷新否则多个请求同时发现 token 过期会瞬间对微信接口发起多次刷新调用直接触发频控。上面的写法加了synchronized单机部署没问题多实例部署的建议用 Redis 的分布式锁或者干脆把 token 存到 Redis 里统一管理。4.3 手机号换取新版 getuserphonenumber 的完整实现后端拿到前端传来的手机号 code 后调微信接口请求地址https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_tokenACCESS_TOKEN请求方式POSTBody 为 JSON{ code: 前端传来的code }完整 Java 代码RestController RequestMapping(/miniapp) public class MiniappPhoneController { Autowired private WxAccessTokenManager accessTokenManager; PostMapping(/phone) public Result getPhone(RequestBody PhoneRequest request) { String accessToken accessTokenManager.getAccessToken(); String url https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token accessToken; JSONObject body new JSONObject(); body.set(code, request.getCode()); String resp HttpUtil.post(url, body.toString()); JSONObject json JSONUtil.parseObj(resp); Integer errcode json.getInt(errcode); if (errcode ! null errcode ! 0) { return Result.error(手机号获取失败错误码 errcode json.getStr(errmsg)); } // 成功返回的手机号信息 JSONObject phoneInfo json.getJSONObject(phone_info); String phoneNumber phoneInfo.getStr(phoneNumber); String purePhoneNumber phoneInfo.getStr(purePhoneNumber); String countryCode phoneInfo.getStr(countryCode); // 这里把手机号绑定到当前用户 Long userId getCurrentUserId(); // 从 token 中解析 userService.bindPhone(userId, purePhoneNumber); return Result.success(ImmutableMap.of( phone, purePhoneNumber )); } }返回结果里常用的三个字段phoneNumber带国家区号的手机号比如 8613812345678。purePhoneNumber不带区号的手机号就是 11 位纯数字我们存库一般用这个。countryCode国家区号中国是 86。4.4 老版 AES 解密方案为什么不推荐了老方案是这样的getPhoneNumber 返回 encryptedData 和 iv后端用 session_key 做 AES-128-CBC 解密从解密后的 JSON 里提取手机号。这个方案在 2023 年之后逐步被微信废弃新创建的小程序不再返回 encryptedData而是直接返回 code。更重要的是老方案把 session_key 暴露给了后端解密环节一旦 session_key 泄露攻击者可以解密该用户所有加密数据安全风险比 code 换手机号大得多。新方案的好处是前端不需要接触任何敏感数据后端拿 code 跟微信服务端换全程敏感信息不落地到前端一层。所以新项目绝对不要用老方案老项目要迁就是一句话的事前端把e.detail.encryptedData改成e.detail.code后端把解密逻辑改成调 getuserphonenumber代码量反而更少。5. 前后端联调的现实问题开发者工具、真机与上架审核5.1 开发者工具的「不校验合法域名」只有调试时能用微信小程序在真机上请求后端接口要求域名必须满足两个条件配置在小程序后台的「request 合法域名」里。必须是 HTTPS不能是 IP不能带端口。开发者工具里有一个「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」的选项勾选后可以用 HTTP 或 IP 直连本地服务方便开发调试。很多新手会在这里踩坑本地联调一切正常一上传体验版用真机打开所有请求全部失败报url not in domain list。正确做联调的方式是两种后端把接口部署到一个 HTTPS 测试环境域名加到小程序后台的 request 合法域名里然后用「预览」模式在真机上调试。或者用开发者工具的「本机调试」功能需要下载调试插件工具会起一个本地代理让真机也能访问你电脑上的本地服务。第一种方式最接近线上第二种方式适合日常开发。注意正式域名不要用免费的 HTTPS 证书微信对证书链的校验很严格证书链不完整也会导致请求失败。5.2 真机预览与手机号模拟的差异开发者工具可以模拟手机号授权但模拟的 code 换回来的手机号是假的。真机调试时用户实际点击授权后微信返回的 code 才是真实的后端才能换到真实手机号。有一个容易被忽略的坑真机调试时当前登录的微信账号必须是小程序的开发者或体验成员。如果你用微信 A 扫码预览但小程序后台没把 A 加为体验成员A 打开的是开发版手机号授权可以正常走但某些接口比如涉及登录态校验、生产环境配置的可能和预期不一致。最好在联调前就确认所有参与测试的微信号都加好了权限。5.3 手机号按钮连点和重复 code 的处理手机号 code 只能使用一次如果用户点了一次授权code 已经发给后端并且被消费了这时用户手滑又点了一次按钮微信会返回一个新的 code旧的 code 作废。这个逻辑本身没问题但如果按钮没有 loading 状态用户在两次网络请求交错时可能提交了同一个 code后端第二次调 getuserphonenumber 会报错。前端加 loading 只是第一步后端也要做幂等处理。我的做法是用一个 Redis key 记录「用户 ID code」的消费状态同一 code 第二次请求直接返回第一次的结果而不是报错。这样即使用户连点也不会因为重复消费而中断流程。另外getuserphonenumber接口有频控虽然官方文档没写死具体数字但我在测试时发现同一个 access_token 短时间内调用太频繁会报45011api minute-quota reach limit之类的错误。所以后端在调用该接口前可以做个简单的限流同一用户 5 分钟内最多调一次。5.4 上架前的隐私指引配置漏了会被打回微信从 2023 年下半年开始强制要求小程序配置「用户隐私保护指引」涉及收集用户信息的接口必须先声明。头像、昵称、手机号都属于典型的用户个人信息必须在微信公众平台的「设置 - 服务内容声明 - 用户隐私保护指引」里逐项勾选声明。没配置隐私指引时真机上调用chooseAvatar或getPhoneNumber会报错错误信息类似privacy permission is not authorized。开发者工具里可以通过「清缓存 - 清除全部缓存」后重新编译来触发隐私弹窗验证。还有一个小细节如果小程序会调用wx.login但登录接口最终会拿到 openid 并创建线上用户这个过程严格来说也涉及收集用户身份信息建议在隐私声明里把「用户身份标识」也列上避免后续审核时被挑刺。6. 数据落库与账号体系设计手机号到底怎么存6.1 手机号绑定的业务设计拿到手机号之后最核心的问题是手机号和用户账号体系怎么联动我见过两种做法。第一种手机号即账号标识。用户授权手机号后后端检查这个手机号是否已经绑定过其他账号如果没有就直接把这个手机号绑定到当前 openid 对应用户如果已经绑定过其他账号就要做冲突处理——提示用户「该手机号已绑定其他账号是否切换登录」或者合并账号。第二种手机号作为可选的补充信息。用户可以用微信身份直接使用小程序手机号是可选填的安全验证手段用于后续的敏感操作校验、客服联系等场景。这种情况下手机号字段是可空的页面展示时要做空值兼容。我个人的建议是第二种更稳妥因为手机号快速验证组件受频控限制如果业务强制依赖手机号用户频繁进入小程序时可能因为频控问题拿不到手机号导致功能不可用。6.2 脱敏存储与日志规范手机号属于敏感个人信息落库时必须做脱敏处理。常规做法是数据库里存完整手机号但查询接口返回时只返回脱敏后的格式比如138****1234。如果业务不需要完整手机号比如只是用来做是否绑定的判断数据库里可以直接存脱敏后的值甚至连加密都不用做。但一般业务都会有短信通知、客服回拨之类的需求所以存完整的也没问题但接口不要直接返回。日志打印时禁止打印完整手机号统一用脱敏工具类处理后再输出。脱敏工具类很简单随手就能写public static String maskPhone(String phone) { if (StringUtils.isBlank(phone) || phone.length() 7) { return phone; } return phone.substring(0, 3) **** phone.substring(7); }6.3 openid、unionid、手机号怎么串起来一个合理的用户表字段设计大概是这样的字段说明id用户主键后端业务里用的用户 IDopenid小程序用户唯一标识同一小程序内唯一unionid开放平台唯一标识多端通用可为空phone用户授权的手机号脱敏后展示完整值加密存储nickname用户填写的昵称avatar_url用户头像的 CDN 地址session_key微信会话密钥仅后端使用不返回前端create_time创建时间openid 是用来识别「同一个用户在同一个小程序里的身份」的手机号是用来做「跨端识别」或「安全验证」的。如果只有一个小程序不需要跨端打通直接用 openid 做用户主键就行手机号作为用户信息的一个字段即可。6.4 退出登录与账号注销怎么处理微信小程序一般不需要做传统的账号密码登录所以「退出登录」通常只是前端清掉本地 token服务端标记一下 token 失效。手机号如果绑定了账号注销时需要向微信的注销接口申请解绑对于 openid 本身没有解绑的概念但涉及手机号这种敏感信息建议在注销流程里把用户表里的手机号字段清空。这个功能建议在开发阶段就设计好等审核时被问到「如何注销账号」才补会非常被动。7. 写在最后几个从踩坑里换来的提醒这里的「获取微信信息、手机号」方案我用同样的思路在不同的项目里落地过多次最后分享几个容易被忽略但影响很大的细节。头像临时路径的问题一定要提前处理。很多人开发时用临时路径测试没发现异常等上了生产环境后用户反馈头像时好时坏其实就是临时路径过期了。此时后端已经存了一堆无效链接还得写脚本清理非常被动。最好的方式是用户选择头像后立即上传别等提交表单时才上传。手机号 code 不要存到日志里。虽然 code 本身有效期只有 5 分钟但如果日志系统被拖库攻击者拿到 code 后可以去换手机号造成隐私泄露。我在项目里用日志脱敏方案统一处理凡是code字段都不打印。这个习惯从小项目开始养成后面接更敏感的数据时就不会慌。还有一点access_token 的刷新机制一定要在生产环境压测一遍。我碰过一次线上事故小程序上线后被多人同时点击手机号授权access_token 在 Redis 里过期后瞬间被多个请求同时刷新触发微信的频控导致手机号获取接口连续报错十几分钟。后来加了分布式锁和预刷新机制才解决。这种问题平时测试根本发现不了只有并发上来才会暴露提前做好防御总没错。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询