第十一章 app.js 全局状态与 openid 获取
📚 系列教程:微信小程序投票系统完整开发
🔗 上一章:第十章 - 结果页 result 开发
🔗 下一章:第十二章 - 后端接口设计与数据库建表
11.1 为什么 openid 如此重要
openid 是微信对用户在某个特定小程序下的唯一标识,具有以下特性:
| 特性 | 说明 |
|---|---|
| 唯一性 | 同一个用户在同一个小程序的 openid 永远不变 |
| 隔离性 | 同一个用户在不同小程序的 openid 不同 |
| 安全性 | 不暴露用户真实微信号,隐私友好 |
| 用途 | 识别用户身份、防重复投票、关联用户数据 |
11.2 openid 获取的完整时序图
小程序端 后端服务 微信服务器 │ │ │ │── 1. wx.login() ───────►│ │ │◄─ 2. 返回临时 code ──────│ │ │ │ │ │── 3. POST /wx/user/login │ │ │ { code: "xxx" } ──►│ │ │ │── 4. GET code2Session ──►│ │ │ appid+secret+code │ │ │◄─ 5. { openid, session } │ │ │ │ │◄─ 6. { code:200, │ │ │ data: openid } ──│ │ │ │ │ │─ 7. 缓存到 Storage ──────┤ │ │─ 8. 通知所有等待回调 ──────┤ │关键点:
code有效期5分钟,且只能使用一次AppSecret只能在后端使用,绝不能放在小程序代码中- 后端调用微信 API 的地址:
https://api.weixin.qq.com/sns/jscode2session
11.3 当前 app.js 完整代码解析
// app.jsApp({globalData:{userInfo:null,openid:'',baseUrl:'https://www.chinahanwucun.cn',_openidReady:false,// openid 是否已就绪_openidCallbacks:[]// 等待 openid 的回调队列},/** * 对外暴露的 openid 获取方法 * 如果 openid 已就绪 → 立即回调 * 如果未就绪 → 加入队列,就绪后统一回调 * * 使用方式(在页面 js 中): * const app = getApp() * app.getOpenid(openid => { ... }) */getOpenid(callback){if(this.globalData._openidReady){callback(this.globalData.openid)}else{this.globalData._openidCallbacks.push(callback)}},/** * 内部方法:openid 就绪,通知所有等待的回调 */_resolveOpenid(openid){this.globalData.openid=openidthis.globalData._openidReady=true// 清空队列,依次执行所有等待的回调constcbs=this.globalData._openidCallbacksthis.globalData._openidCallbacks=[]cbs.forEach(cb=>cb(openid))},onLaunch(){// 步骤1:优先读取缓存,让页面请求能立即发出(不用等待登录完成)constcached=wx.getStorageSync('openid')if(cached){this._resolveOpenid(cached)}// 步骤2:每次启动都重新登录(刷新 session,微信 session 有效期约7天)wx.login({success:res=>{if(res.code){wx.request({url:`${this.globalData.baseUrl}/wx/user/login`,method:'POST',header:{'content-type':'application/json'},data:{code:res.code},success:r=>{if(r.data&&r.data.data){constopenid=r.data.data wx.setStorageSync('openid',openid)// 更新缓存// 如果之前没有缓存(首次使用),现在通知等待的回调if(!cached){this._resolveOpenid(openid)}else{// 已经通知过了,只更新全局变量即可this.globalData.openid=openid}}elseif(!cached){this._fallbackOpenid()}},fail:()=>{if(!cached)this._fallbackOpenid()}})}},fail:()=>{if(!cached)this._fallbackOpenid()}})},/** * 降级策略:当网络异常或接口异常时,生成一个临时 ID * 用 'tmp_' 前缀区分,后端可做相应处理 */_fallbackOpenid(){letopenid=wx.getStorageSync('openid')if(!openid){openid='tmp_'+Date.now()+'_'+Math.random().toString(36).substr(2,8)wx.setStorageSync('openid',openid)}this._resolveOpenid(openid)}})11.4 回调队列机制详解
这是一个经典的异步初始化 + 等待队列模式:
// 场景:页面 A 和页面 B 同时加载,都需要 openid// 页面 A onLoad(openid 还没好)app.getOpenid(openid=>{// 此回调被推入 _openidCallbacks 队列fetchVoteList(openid)})// 页面 B onLoad(openid 还没好)app.getOpenid(openid=>{// 此回调也被推入 _openidCallbacks 队列fetchUserInfo(openid)})// 此时 _openidCallbacks = [fetchVoteList回调, fetchUserInfo回调]// ... 等待 wx.login + 后端接口 ...// openid 获取成功,_resolveOpenid 被调用// → 依次执行队列中所有回调// → fetchVoteList 和 fetchUserInfo 同时收到 openid 并执行11.5 常见错误与解决方案
错误1:直接读取 globalData(时序问题)
// ❌ 错误:app.js 还在异步获取 openid,这里可能拿到空字符串onLoad(){constopenid=app.globalData.openid// 可能是 ''wx.request({data:{openid}})// 请求带了空 openid}// ✅ 正确:使用回调,确保 openid 就绪后再发请求onLoad(){app.getOpenid(openid=>{wx.request({data:{openid}})// openid 一定有值})}错误2:code 被多次使用
// ❌ 错误:每次需要 openid 都调用 wx.login// wx.login 返回的 code 只能用一次,多次调用会导致之前的 code 失效// ✅ 正确:只在 app.js onLaunch 中调用一次 wx.login// 后续通过 getOpenid() 方法复用已获取的 openid错误3:AppSecret 放在前端
// ❌ 严重错误:小程序代码可以被反编译,AppSecret 会泄露!wx.request({url:'https://api.weixin.qq.com/sns/jscode2session',data:{appid:'xxx',secret:'AppSecret泄露了!!!',js_code:code}})// ✅ 正确:code 发给自己的后端,由后端持有 AppSecret 去换 openidwx.request({url:'https://your-server.com/wx/user/login',data:{code:code}// AppSecret 在后端的 application.properties 中配置})11.6 全局状态管理扩展
除了 openid,app.js 还可以管理其他全局状态:
App({globalData:{openid:'',baseUrl:'https://www.chinahanwucun.cn',// 扩展:用户信息缓存userInfo:null,// 扩展:全局配置(从后端拉取)config:{maxOptionsPerVote:10,voteTitleMaxLength:50}},// 扩展:检查网络状态checkNetwork(callback){wx.getNetworkType({success(res){if(res.networkType==='none'){wx.showToast({title:'当前无网络连接',icon:'none'})callback(false)}else{callback(true)}}})}})本章小结
✅ 深入理解了 openid 的唯一性和获取时序
✅ 完全理解了回调队列机制(异步初始化模式)
✅ 掌握了正确使用getOpenid()的方式
✅ 知道了 3 个典型错误场景及避坑方法
下一章:后端接口设计与完整的数据库建表语句。
