
HBuilderX 配 uni-app 写微信小程序授权登录网上教程一抓一大把但大多是把代码贴一遍完事。等你真动手做才会撞上那些教程里没写的东西弹窗不弹、头像默认灰的、code 换 openid 报 40029、开发者工具好好的真机一测就废。这篇文章不做复读机把我在真实项目里跑通这套授权的完整链路拆给你看从原理到代码再到排坑一次讲透。1. 项目拆解授权登录的本质与选型逻辑1.1 微信小程序的登录链路到底长什么样很多人以为小程序授权登录就是弹个窗点允许然后拿到昵称头像。实际上真正的核心并不在用户信息而在于身份识别。小程序是运行在微信容器里的前端拿到的用户身份信息可以伪造后端不能信任。所以微信设计了一套基于code的登录逻辑小程序端调用uni.login获取一个临时凭证code有效期只有 5 分钟且只能用一次前端把code发给自己的后端后端拿着code加上小程序的AppID和AppSecret调微信的jscode2session接口换回openid用户的唯一身份标识和session_key会话密钥后端再拿openid去自己数据库里查或建用户签发自家的token返回前端前端存下token后续请求带着它后端据此识别用户。也就是说code只是中转凭证真正的身份锚点是openid。这也是为什么授权登录必须要有后端配合纯前端无法闭环。而用户信息授权是另一条线。早期可以直接wx.getUserInfo弹窗拿头像昵称后来微信改成了getUserProfile再后来也就是现在官方又收了回去推荐用头像昵称填写能力。所以你会发现很多老教程代码跑不通不是代码错了是平台规则变了。1.2 为什么选择 HBuilderX uni-app 这套组合先明确一个前提微信小程序原生开发也能做登录但如果你有过跨端需求甚至已经在用 uni-app那这套组合的优势很明显API 封装统一uni.login在微信小程序下自动对应wx.login同时保留多端兼容Vue 语法开发体验好模板、组件、生命周期都是熟悉的味道新手也容易上手HBuilderX 对小程序调试支持完善可以直接运行到微信开发者工具甚至一键发行生态成熟登录页、请求封装这些代码稍加改造即可复用到 App、H5。唯一的代价是你必须理解 uni-app 的 API 在微信平台上的映射关系否则遇到平台差异会在排错时懵掉。理解了这点下面所有实现才有意义。2. 开发前准备从 AppID 到项目初始化工欲善其事必先利其器微信授权登录每个环节都依赖配置项。缺一个 AppID或者 secret 不对后面全是白忙活。2.1 小程序账号注册与 AppID 获取第一步是在微信公众平台注册一个小程序账号。如果你已经有账号直接在开发管理 - 开发设置里找到AppID和AppSecret。这里有两个坑要注意AppSecret只在第一次生成时完整展示后续只能重置。忘了就重新生成旧 secret 立即失效如果你之前有其他服务在调用微信接口重置前评估一下影响一定要在开发设置里把服务器域名配置好后面请求后端接口要用。开发调试期间可以暂时在开发者工具里勾选不校验合法域名但发布前必须配好而且必须是已备案的 HTTPS 域名同时要求 ICP 备案主体与小程序主体一致。2.2 HBuilderX 创建 uni-app 项目打开 HBuilderX新建项目选择uni-app模板我习惯选 Vue 3 版本除非你的团队还停留在 Vue 2。项目结构里重点关心这几个位置文件/目录作用pages/页面目录登录页就放这里manifest.json全局配置小程序的 AppID 在这里填pages.json页面路由与导航栏配置utils/或common/自己建的放请求封装等公共代码static/静态资源在manifest.json的微信小程序配置里填入刚才拿到的AppID这是让微信开发者工具认识你这个项目的第一步。2.3 运行到微信开发者工具在 HBuilderX 菜单栏选择运行 - 运行到小程序模拟器 - 微信开发者工具。前提是你本地已经装了微信开发者工具并在它的设置 - 安全设置里打开了服务端口。如果点了运行没反应大概率是端口没开或者 HBuilderX 没识别到工具安装路径。到这里环境就绪接下来进入正题。3. 核心实现登录页到后端换 token 的完整代码我先给结论现在做微信小程序登录最省心的组合是uni.login拿 code 头像昵称填写组件收用户信息 后端code2Session换 openid 签发 token。下面每个环节拆开讲。3.1 登录页 UI 与新版头像昵称填写微信官方从 2022 年 10 月 25 日之后调整了用户信息相关能力传统的getUserProfile不再弹窗引导用户授权真实头像昵称而是返回默认灰色头像和微信用户这样的匿名信息。所以新项目不要再去折腾getUserProfile直接用官方推荐的能力。新版做法是用button的open-typechooseAvatar让用户选择头像用input的typenickname让用户填写或复用微信昵称。这一段在 uni-app 里写起来也不复杂template view classlogin-container view classprofile-card button classavatar-btn open-typechooseAvatar chooseavataronChooseAvatar image v-ifavatarUrl classavatar :srcavatarUrl/image view v-else classavatar-placeholder点击选择头像/view /button input classnickname-input typenickname v-modelnickname placeholder请输入昵称 / /view button classsubmit-btn typeprimary :loadingloading clickhandleLogin 微信登录 /button /view /templatescript setup import { ref } from vue const avatarUrl ref() const nickname ref() const loading ref(false) const onChooseAvatar (e) { // e.detail.avatarUrl 只是临时路径后面要上传到自己的服务器 avatarUrl.value e.detail.avatarUrl } const handleLogin () { if (!avatarUrl.value) { uni.showToast({ title: 请选择头像, icon: none }) return } if (!nickname.value) { uni.showToast({ title: 请填写昵称, icon: none }) return } // 这里继续执行登录流程 doLogin() } /script有个关键细节chooseAvatar返回的是本地临时文件路径不能直接当成正式的图片地址存到后台。你应该先把这张图片通过uni.uploadFile上传到自己的服务器或者对象存储拿到一个网络图片 URL再和 code、昵称一起提交给后端。如果省掉这一步会出现本地能看、真机上刷新后头像裂了的情况。3.2 获取 codeuni.login 的正确打开方式uni.login是整套登录链路的第一环它拿到的code是后端换 openid 的唯一凭证。代码很简单但有几个点值得注意const getWxCode () { return new Promise((resolve, reject) { uni.login({ provider: weixin, success: (res) resolve(res.code), fail: (err) reject(err) }) }) }code是一次性的使用过后立即失效。所以不要提前获取等所有资料齐全、准备调后端接口前再拿防止因为用户操作耗时导致 code 过期code有效期 5 分钟后端换 openid 延迟较高的话前端重试时应该重新调用uni.login而不是把旧 code 再发一遍provider: weixin在仅做微信小程序时可以省略但显式写出来更容易让看代码的人理解意图。3.3 提交登录上传头像 发送 code 给后端我习惯把上传头像和登录请求拆成两个步骤因为头像上传失败和登录失败是两类错误混在一起不好排查。const doLogin async () { loading.value true try { // 1. 上传头像拿到正式 URL const uploadResult await uploadAvatar(avatarUrl.value) if (!uploadResult.url) throw new Error(头像上传失败) // 2. 获取 code const code await getWxCode() // 3. 调后端登录接口 const apiResult await api.login({ code, nickname: nickname.value, avatarUrl: uploadResult.url }) if (apiResult.code 0) { // 4. 保存 token uni.setStorageSync(token, apiResult.data.token) uni.setStorageSync(userInfo, apiResult.data.userInfo) uni.showToast({ title: 登录成功, icon: success }) setTimeout(() { uni.switchTab({ url: /pages/index/index }) }, 500) } else { uni.showToast({ title: apiResult.msg || 登录失败, icon: none }) } } catch (error) { console.error(登录异常:, error) uni.showToast({ title: 登录异常请重试, icon: none }) } finally { loading.value false } }这里我用了uploadAvatar和api.login两个封装好的方法。uploadAvatar内部调用uni.uploadFile上传成功后后端返回图片地址api.login走的就是下面要讲的统一请求封装。这两个函数的具体实现不复杂重点是理解整个编排顺序。3.4 后端接口code2Session 与自定义 token后端这一半很容易被前端同学忽略但它才是登录的真正核心。这里用 Node.js 写个最小可用的示例逻辑照样可以翻译成任何后端语言const express require(express) const axios require(axios) const crypto require(crypto) const router express.Router() const APPID 你的小程序AppID const SECRET 你的小程序AppSecret router.post(/api/login, async (req, res) { const { code, nickname, avatarUrl } req.body if (!code) return res.json({ code: 400, msg: 缺少code }) try { // 1. code 换 session_key 和 openid const url https://api.weixin.qq.com/sns/jscode2session?appid${APPID}secret${SECRET}js_code${code}grant_typeauthorization_code const { data } await axios.get(url) if (data.errcode) { // 常见错误40029 code无效、40125 secret错误 console.error(jscode2session 错误:, data) return res.json({ code: data.errcode, msg: data.errmsg }) } const { openid, session_key } data // 2. 根据 openid 查用户不存在则创建 let user await findUserByOpenid(openid) if (!user) { user await createUser({ openid, nickname, avatarUrl }) } // 3. 签发自定义登录态 token const token crypto.randomBytes(32).toString(hex) await saveToken(token, { openid, userId: user.id }) // 4. 返回给前端 res.json({ code: 0, data: { token, userInfo: user } }) } catch (error) { console.error(login error:, error) res.json({ code: 500, msg: 服务器内部错误 }) } }) module.exports router这里有个决策点为什么后端要自己签发 token而不是直接把 session_key 给前端因为session_key是微信颁发给业务后端的敏感凭证它后续要配合解密手机号、运动数据等接口使用。如果把它透传给前端等于把解密能力暴露给客户端存在被滥用的风险。后端自制 token 相当于把会话管理权握在自己手里token 本身不需要有任何含义随机字符串加存储即可后续通过 token 换取用户身份。session_key存不存、存哪里取决于业务需求如果你只做登录换完 openid 就可以丢弃如果要解密手机号就得在数据库或缓存里和 openid 关联保存。我建议在会话存储里带上它因为现在很多业务都会做手机号快捷登录。3.5 统一请求封装与登录态管理登录拿到 token 后前端所有业务请求都应该带上它。写一个公共的 request 方法避免每个页面重复处理 token 逻辑// utils/request.js const BASE_URL https://api.example.com const request (options) { return new Promise((resolve, reject) { const token uni.getStorageSync(token) uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { // 统一处理业务码 if (res.data.code 401) { // token 过期清本地并跳登录页 uni.removeStorageSync(token) uni.removeStorageSync(userInfo) uni.reLaunch({ url: /pages/login/login }) uni.showToast({ title: 请重新登录, icon: none }) return } resolve(res.data) }, fail: (err) { reject(err) } }) }) } module.exports request同时建议在每个需要登录态的页面onShow或onLoad里检查 tokenonShow() { const token uni.getStorageSync(token) if (!token) { uni.reLaunch({ url: /pages/login/login }) return } }这一步看似简单却是实际项目中最容易漏掉的。很多人只在 App 启动时检查一次结果用户 token 过期后页面还能打开请求全部 401体验非常差。另外如果你做了多端兼容比如也要跑在 App 端uni.login在 App 端的表现和微信小程序不同需要单独处理这里先按下不表专注微信小程序场景。4. 联调与发布从开发者工具到真机代码写完了不代表登录就能通。联调环节是问题高发区我按实际流程给你过一遍。4.1 HBuilderX 运行到微信开发者工具这是开发期最高频的操作。点 HBuilderX 的运行菜单选微信开发者工具代码改了保存后会自动热更新比手动刷新省事。但有个常见问题HBuilderX 提示运行失败请先启动微信开发者工具而你明明已经启动了。原因通常是微信开发者工具的安全设置 - 服务端口没有打开。打开这个开关后重启工具一般就能连上。4.2 合法域名配置与关闭校验开发调试时微信开发者工具默认会拦截非白名单域名的请求。你后端的https://api.example.com必须在微信公众平台开发设置 - 服务器域名里配置到request 合法域名。这里有个容易被坑的点本地调试后端比如 http://localhost:8080是永远配不进合法域名的因为微信要求域名不能是 IP 和 localhost且必须 HTTPS。开发阶段的两个应对办法在微信开发者工具的详情 - 本地设置里勾选不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书这是最常用的做法把后端临时部署到一个 HTTPS 的测试环境域名上。记得在正式发布前把所有需要的域名都配置好否则真机上会直接请求失败。4.3 真机预览与体验版工具联调通过就要上真机。在微信开发者工具里点预览会生成一个二维码手机扫码即可在真机上打开小程序。真机调试重点关注三件事域名校验手机上没有关闭校验的选项所以合法域名必须真实可访问HTTPS 证书有些自签名证书在开发者工具里能过真机会报网络异常务必使用正规 CA 证书授权弹窗与键盘行为头像选择、昵称输入在真机上的交互和工具里体验不完全一样像部分安卓机型对typenickname的键盘风格支持不同建议在主流机型上各点一遍。5. 高频踩坑与排查手册我把实际项目里撞过的坑整理成速查表再挑几个最典型的展开说基本覆盖了授权登录的大部分异常场景。5.1 常见问题速查表问题现象原因分析解决方案登录弹窗不出现或者点了没反应基础库版本低、或使用了失效的getUserInfo升级基础库改用chooseAvatar与nickname输入获取的头像是灰色默认头像官方调整后getUserProfile不再返回真实信息放弃getUserProfile走新版头像昵称填写后端收到openid但每次请求都是同一用户时出现数据错乱没有按 openid 作为用户唯一键处理数据库给 openid 加唯一索引code 换取失败报 40029code 已过期、已被使用、AppID/secret 不在同一账号重新调用uni.login获取新 code报 40125 或 invalid secretAppSecret 错误或被重置核对 secret必要时在公众平台重置请求报url not in domain list未配置 request 合法域名在小程序后台配置域名并确保 HTTPS开发者工具正常、真机登录失败域名校验差异、证书问题真机走正式域名检查证书链完整性5.2 重点排查授权弹窗不出现如果你还用老的uni.getUserInfo拿用户信息现在大概率弹窗已经不会跳了。这是微信平台策略调整的结果不是你的代码 bug。正确路径是头像通过按钮的open-typechooseAvatar触发系统选择器昵称通过typenickname的 input 获取。这里没有传统意义上的授权弹窗了登录体验变成用户主动选择头像和填写昵称反而更符合微信的隐私保护思路。5.3 重点排查code 换 openid 失败code的问题有三个高发点一次uni.login拿到的 code 只能换一次 openid如果你用同一个 code 连续调两次后端第二次必定失败。调试时可以把后端日志打印出来确认每次登录流程是否只消费一次code 有效期只有 5 分钟前端拿到 code 后如果网络慢、用户停留久可能过期。前端在发起登录前再取 code 最安全AppID 和 AppSecret 必须属于同一个小程序。很多同学会从不同项目里复制配置结果 AppID 是 A 小程序的secret 是 B 小程序的直接 40125。5.4 重点排查真机与工具的差异最让人崩溃的是工具里一切正常真机上一登录就失败。最常见的差异点就是域名。开发者工具里有不校验合法域名这个开关一旦打开工具里用的是本地后端、IP 直连都能跑通而真机上所有请求都必须走合法 HTTPS 域名。所以联调后端时尽量从一开始就使用正式的测试域名不要把关闭校验当成习惯。另外还遇到过一种情况安卓手机上chooseAvatar选择了图片后临时路径在短时间内有效但如果你把临时路径直接提交后端后端后续访问时会 404。正确的做法是马上用uni.uploadFile把图片上传转存不要等用户填完昵称再一起提交。最后再分享一个小技巧登录成功后别只存 token 和 userInfo顺手把登录时间uni.setStorageSync(loginTime, Date.now())也存下来。这样你可以根据 token 的有效期在本地做一个预过期判断比如 token 有效期 7 天你可以在第 6 天就主动让用户重新登录而不是等到请求 401 才被动踢出体验会平滑很多。这个小改动成本几乎为零但用户感知差别挺明显的。授权登录这套链路前端代码只是冰山一角真正决定稳不稳的是你对 code、openid、token 之间关系的理解。先把这条链路画通再动手写代码踩坑的概率会小一大半。