"个人中心"小程序:用户登录
目录
案例分析
用户登录是小程序必不可少的环节。
一个完整的登录功能包括用户的信息获取、登录的状态判定等。
前导知识
用户登录流程
三个角色:
- 小程序(客户端)
- 开发者服务器
- 微信接口服务
登录流程:
- 小程序获取code
- 小程序将code发送给开发者服务器
- 开发者服务器通过微信接口服务校验登录凭证
需要注意的是,openid并不等同于微信用户id ,同一个微信用户在不同Appid小程序中的openid是不同的。
- 开发者服务器自定义登录态
开发者服务器生成一个自定义token(令牌)给小程序,通过token可以查询到openid和session_key,下次再请求只要携带token就行。
数据缓存
在小程序中可以缓存一些数据,从而在小程序退出后再次打开时,可以从缓存中读取上次保存的数据。
搭建开发者服务器
本节使用Node.js搭建开发者服务器,
如果还没有安装node.js的可以参考 Windows 安装Node.js
安装成功后,创建一个空目录作为项目目录,然后打开命令提示符切换到该目录
1)初始化项目,将会自动创建package.json配置文件
npm init -y
2)安装Express框架和request模块
cnpm install express --save cnpm install request --save
3) 安装nodemon监控文件修改(如果已经安装则跳过此步)
cnpm install nodemon -g
4)执行上述命令后,在项目目录下创建index.js文件:
// 加密解密 //const crypto = require('crypto'); //const WXBizDataCrypt = require('./WXBizDataCrypt') const express = require('express') const bodyParser = require('body-parser') const request = require('request') const app = express() app.use(bodyParser.json()) const wx = { appid: '',//需要填写开发者的AppId secret: ''//需要填写开发者的AppSecret } var db = { session: {}, user: {} } app.post('/login', (req, res) => { // 注意:小程序端的appid必须使用真实账号,如果使用测试账号,会出现login code错误 console.log('login code: ' + req.body.code) var url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + wx.appid + '&secret=' + wx.secret + '&js_code=' + req.body.code + '&grant_type=authorization_code' request(url, (err, response, body) => { console.log('session: ' + body) var session = JSON.parse(body) if(session.openid) { var token = 'token_' + new Date().getTime() db.session[token] = session if(!db.user[session.openid]) { db.user[session.openid] = { credit: 100 } } } res.json({ token: token }) }) }) app.listen(3000, () => { console.log('server running at http://127.0.0.1:3000') })
- 第17-19行是校验登录凭证的微信接口URL地址,在接口参数中 js_code来自小程序提交的code
- 第10-13行用于模拟数据库保存数据
- 第24行用于生成token
- 第25行用于保存openid和session_key,保存后,第31行将token响应给小程序。
- 第26~29行在db.user中保存了用户记录,相当于在服务器中注册了一个新用户,每个新用户保存100积分
保存上述代码后,执行如下命令、启动开发者服务器:
nodemon index.js
[nodemon] 2.0.12 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node index.js` server running at http://127.0.0.1:3000
实现用户登录
在微信开发者工具中创建一个空白项目,
新建app.js和app.json文件以及pages/index/index页面
然后在app.js中编写如下代码,实现小程序启动时自动登录。
App({ onLaunch: function () { this.login() }, login: function () { wx.login({ success: res => { console.log('login code: ' + res.code) wx.request({ url: 'http://127.0.0.1:3000/login', method: 'post', data: { code: res.code }, success: res => { console.log('token: ' + res.data.token) // 将token保存为公共数据(用于在多页面中访问) this.globalData.token = res.data.token // 将token保存到数据缓存(下次打开小程序无需重新获取token) wx.setStorage({ key: 'token', data: res.data.token }) } }) } }) }, globalData: { userInfo: null, token: null // 保存token } })
上述代码执行后,会在控制台中看到login code和token的值:
login code: 021HGy0w3M9zXW2E3p0w3d7ske4HGy0F token: token_1629600794764
在开发者服务器的控制台中,可以看到login code 和session的值。
server running at http://127.0.0.1:3000 login code: 021HGy0w3M9zXW2E3p0w3d7ske4HGy0F session: {"session_key":"354jtFe9AMkjPFUxhclH8A==","openid":"olaWZ5QrlbsK75dsU6DibtNzIQwE"}
可以看到token的值,说明小程序已经能收到服务器返回的token
测试
为了测试小程序能否使用token获取用户信息,
在服务器端index.js文件的app.listen()方法前面编写如下代码:
pp.get('/credit', (req, res) => { var session = db.session[req.query.token] //凭借token获取信息 if(session && db.user[session.openid]) { res.json({ credit: db.user[session.openid].credit }) } else { res.json({ err: '用户不存在,或未登录。' }) } })
在pages/index/index.wxml文件中编写"获取用户的积分"按钮,
<button bindtap="credit">获取用户积分</button>
在pages/index/index.js文件中编写credit()函数,
credit: function () { wx.request({ url: 'http://127.0.0.1:3000/credit', data: { token: app.globalData.token }, success: res => { console.log(res.data) } }) },
运行小程序,会从控制台中看到服务器返回的积分:
检查用户是否已经登录
上一节已经实现了用户登录,
下次启动小程序时,应该判断数据缓存中是否存在token,如果存在,直接取出这个token即可,不用再执行登录操作。
如果token过期,需要重新登录。
服务端
首先,在服务器端index.js文件中的app.listen()方法前面添加代码,检查token是否有效。
app.get('/checklogin', (req, res) => { var session = db.session[req.query.token] console.log('checklogin: ', session) // 将用户是否已经登录的布尔值返回给客户端 res.json({ is_login: session !== undefined }) })
上述代码中,第5行判断根据token取出的session是否为undfined,如果是,说明token已经过期,如果不是,说明token有效。
小程序端
然后在app.js中编写checkLogin()函数,用于判断token是否存在,如果token存在,则请求服务器,判断token是否有效。
checkLogin: function (callback) { var token = this.globalData.token //先看看全局变量中有没有token if (!token) { // 如果没有,从数据缓存中获取token token = wx.getStorageSync('token') if (token) { this.globalData.token = token } else { callback({ is_login: false }) return } } wx.request({ url: 'http://127.0.0.1:3000/checklogin', data: { token: token }, success: res => { callback({ is_login: res.data.is_login }) } }) },
接下来修改app.js中的onLaunch()函数的代码,用于在启动时检查用户是否已登录
如果没有登录,则执行登录操作。
onLaunch: function () { this.checkLogin(res => { console.log('is_login: ', res.is_login) if (!res.is_login) { this.login() } }) },
保存上述代码,运行程序,可以看到在token失效的情况下,小程序进行了重新登录。
is_login: false app.js? [sm]:13 login code: 051M6A100ClZiM1sK1000uIr3w3M6A1J app.js? [sm]:19 token: token_1629709485623
获取用户信息
小程序中,获取用户信息有两种常用的方式,一种是<open-data>组件,不需要用户授权,就可以显示用户头像、昵称、性别等,适合只用来展示的情况。另一种方式是通过单机按钮,提示用户授权,获取到用户信息数据。
方法一:open-data组件
pages/index/index.wxml:
<!-- 用户头像 --> <open-data type="userAvatarUrl"></open-data> <!-- 用户昵称 --> <open-data type="userNickName"></open-data> <!-- 用户性别 --> <open-data type="userGender" lang="zh_CN"></open-data>
方法二:单击按钮授权
接下来将代码改为单击按钮授权的方式,
<!--index.wxml--> <view class="container"> <view class="userinfo"> <block wx:if="{{canIUseOpenData}}"> <view class="userinfo-avatar" bindtap="bindViewTap"> <open-data type="userAvatarUrl"></open-data> </view> <open-data type="userNickName"></open-data> </block> <block wx:elif="{{!hasUserInfo}}"> <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button> <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button> <view wx:else> 请使用1.4.4及以上版本基础库 </view> </block> <block wx:else> <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image> <!-- 用户昵称 --> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> </view> </view>
这一版本中,如果没有用户信息,需用户主动点击按钮才会显示。
相应地,在index.js文件中增加data数据和getUserInfo()函数
// index.js // 获取应用实例 const app = getApp() Page({ data: { motto: 'Hello World', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse('button.open-type.getUserInfo'), canIUseGetUserProfile: false, canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName') // 如需尝试获取用户信息可改为false }, // 事件处理函数 bindViewTap() { wx.navigateTo({ url: '../logs/logs' }) }, onLoad() { if (wx.getUserProfile) { this.setData({ canIUseGetUserProfile: true }) } }, getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { console.log(res) this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) }, getUserInfo(e) { // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息 console.log(e) this.setData({ userInfo: e.detail.userInfo, hasUserInfo: true }) } }) }
在pages/index/index.wxss文件中编写页面样式:
/**index.wxss**/ .userinfo { display: flex; flex-direction: column; align-items: center; color: #aaa; } .userinfo-avatar { overflow: hidden; width: 128rpx; height: 128rpx; margin: 20rpx; border-radius: 50%; }