7.1. 用户管理¶
7.1.1. 功能概述¶
本文档主要介绍用户管理功能,您可以通过本文示例体验用户注册、登录、退出登录功能。
7.1.2. 体验功能¶
7.1.3. 体验 DEMO¶
本章的案例代码,请参考 tcb-demo-basic 。
7.1.4. DEMO 接入流程¶
代码下载完成后,请按照以下步骤操作:
在云开发中创建好至少一个环境。
在 app.js 文件中,如果使用默认环境,则没有改动。 如果需要使用非默认环境,则需要在 wx.cloud.init 中传入环境 ID。 如果使用非默认环境,在云函数的 cloud.init 处,也需要补充环境 ID。
如下图所示:
示例代码如下:
wx.cloud.init({ env: 'xxxx', // 环境 id traceUser: true });
在云函数根目录 cloud/functions 中找到函数 user-login-register 和 user-session, 在两者的 config 目录中新建 index.js,填入小程序的密钥 AppSecret,用 npm 安装两个云函数的依赖,并上传两个云函数。
在云开发的数据库中,新建 collection,名为 users。
7.1.5. 源码介绍¶
7.1.5.1. 准备工作¶
用户管理,包括用户的信息(昵称、性别、头像等的获取)、注册、登录、鉴权等。 本节主要介绍云开发如何操作用户管理。
开发前,建议先阅读以下相关的文档:微信登录能力优化 和 获取用户信息。
参考常用的小程序,例如知乎大学、百果园、摩拜等。
基本的流程:
用户授权小程序可获取用户的开放数据。
选择登录方式(微信绑定的手机/用户的其它手机)。
如果选用了微信绑定的手机,直接信任,注册/登录成功,而如果选用其它手机,则还需要通过发送短信进行手机验证。
7.1.5.2. 用户登录、注册与信息¶
用户的登录、注册、获取信息过程,涉及到以下接口。 详情请参考 用户信息 文档。
wx.getSetting,看看用户有没有授权小程序,可以获取昵称、头像、性别等的用户信息。
wx.getUserInfo (旧版) / button (新版),授权后,可通过此接口/组件获取用户信息。
wx.checkSession,获取 session_key 后,要检查 session_key 是否过期。
如果 session_key 过期,则通过 wx.login 获取 code 后,在云函数中调用 code2Session 更新 session_key。
通过 button 组件,获取手机号码加密数据,并在云函数中通过之前取得的 session_key 进行解密获取真实手机号码。
流程图如下所示:
7.1.5.3. 用户授权¶
对于小程序来说,必须进行用户授权,才能在后续中获取用户的开放数据。 因此,我们在 onLoad 生命周期里,做了以下处理:
旧版: 做授权的检测。
新版: 在模板文件中,则设置授权按钮。
授权后,请将用户数据存入临时的对象中,示例代码如下:
onLoad: async function (options) {
this.db = wx.cloud.database();
this.checkAuthSetting();
this.checkUser();
},
// 检测权限,在旧版小程序若未授权会自己弹起授权
checkAuthSetting() {
wx.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
wx.getUserInfo({
success: async (res) => {
if (res.userInfo) {
const userInfo = res.userInfo
// 将用户数据放在临时对象中,用于后续写入数据库
this.setUserTemp(userInfo)
}
const userInfo = this.data.userInfo || {}
userInfo.isLoaded = true
this.setData({
userInfo,
isAuthorized: true
})
}
})
} else {
this.setData({
userInfo: {
isLoaded: true,
}
})
}
}
})
},
// 设置临时数据,待 “真正登录” 时将用户数据写入 collection "users" 中
setUserTemp(userInfo = null, isAuthorized = true, cb = () => {}) {
this.setData({
userTemp: userInfo,
isAuthorized,
}, cb)
},
// 手动获取用户数据
async bindGetUserInfoNew(e) {
const userInfo = e.detail.userInfo
// 将用户数据放在临时对象中,用于后续写入数据库
this.setUserTemp(userInfo)
},
<button
wx:if="{{userInfo.isLoaded && !isAuthorized && !userInfo.nickName}}"
class="weui-btn"
type="primary"
open-type="getUserInfo"
bindgetuserinfo="bindGetUserInfoNew"
>
授权微信后登录
</button>
7.1.5.4. 数据解密¶
由于某些数据的安全性问题,需要在后台服务进行解密,譬如手机号码,我们可以借助云开发的云函数去完成。
通过 checkUser 方法,检测 session_key 是否已经过期;如果过期,则重新设置,并将用户写入数据库中。 此逻辑通过 updateSession 方法和云函数 user-session 共同完成。
示例代码如下:
// 检测小程序的 session 是否有效
async checkUser() {
const Users = this.db.collection('users')
const users = await Users.get()
console.log(users)
wx.checkSession({
success: () => {
// session_key 未过期,并且在本生命周期一直有效
// 数据里有用户,则直接获取
if (users.data.length && this.checkSession(users.data[0].expireTime || 0)) {
this.setUserInfo(users.data[0])
} else {
this.setUserInfo()
// 强制更新并新增了用户
this.updateSession()
}
},
fail: () => {
// session_key 已经失效,需要重新执行登录流程
this.updateSession()
}
})
},
// 更新 session_key
updateSession() {
wx.login({
success: async (res) => {
console.log(res)
try {
await wx.cloud.callFunction({
name: 'user-session',
data: {
code: res.code
}
})
} catch (e) {
console.log(e)
}
}
})
},
以下是云函数 user-session 的源码,它的主要作用是通过 wXMINIUser.codeToSession 方法获取最新 session_key 。
如果有数据,则仅更新 session_key,
如果没数据则添加该用户并插入 sesison_key。 此 session_key 与用户的数据关联,为后续进行数据解密打下了基础。
// 云函数入口函数
exports.main = async (event) => {
console.log(event)
const db = cloud.database()
const {
OPENID,
APPID
} = cloud.getWXContext()
const wXMINIUser = new WXMINIUser({
appId: APPID,
secret
})
const code = event.code // 从小程序端的 wx.login 接口传过来的 code 值
const info = await wXMINIUser.codeToSession(code)
try {
// 查询有没用户数据
const user = await db.collection('users').where({
_openid: OPENID
}).get()
// 如果有数据,则只是更新 `session_key`,如果没数据则添加该用户并插入 `sesison_key`
if (user.data.length) {
await db.collection('users').where({
_openid: OPENID
}).update({
data: {
session_key: info.session_key
}
})
} else {
await db.collection('users').add({
data: {
session_key: info.session_key,
_openid: OPENID
}
})
}
} catch (e) {
return {
message: e.message,
code: 1,
}
}
return {
message: 'login success',
code: 0
}
}
当我们在数据中存入了用户的一条空数据以及它相关的 session_key 后,我们可以引导用户通过小程序获取微信绑定的手机号,实现快速登录。 在模板文件中,我们添加了一个 button 组件,并将 open-type 设置为 getPhoneNumber。
<button
wx:if="{{userInfo.isLoaded && isAuthorized && !userInfo.phoneNumber}}"
class="weui-btn"
type="primary"
open-type="getPhoneNumber"
bindgetphonenumber="bindGetPhoneNumber"
>
微信快速登录
</button>
单击【微信快速登录】后,请调用 bindGetPhoneNumber,将存放于临时对象的用户开放数据, 以及加密的微信手机数据,发送到 user-login-register 进行解密,并存入用户的数据中。
// 获取用户手机号码
async bindGetPhoneNumber(e) {
// console.log(e.detail);
wx.showLoading({
title: '正在获取',
})
try {
const data = this.data.userTemp
const result = await wx.cloud.callFunction({
name: 'user-login-register',
data: {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
user: {
nickName: data.nickName,
avatarUrl: data.avatarUrl,
gender: data.gender
}
}
})
console.log(result)
if (!result.result.code && result.result.data) {
this.setUserInfo(result.result.data)
}
wx.hideLoading()
} catch (err) {
wx.hideLoading()
wx.showToast({
title: '获取手机号码失败,请重试',
icon: 'none'
})
}
},
详细解密数据的原理,请参考 开放数据校验与解密 。
const WXBizDataCrypt = require('./WXBizDataCrypt');
const {
appId,
secret
} = require('./config');
const cloud = require('wx-server-sdk');
const duration = 24 * 3600 * 1000; // 开发侧控制登录态有效时间
cloud.init();
// 云函数入口函数
const cloud = require('wx-server-sdk')
const WXBizDataCrypt = require('./WXBizDataCrypt')
const duration = 24 * 3600 * 1000 // 开发侧控制登录态有效时间
cloud.init()
// 云函数入口函数
exports.main = async (event) => {
const {
OPENID,
APPID
} = cloud.getWXContext()
const db = cloud.database()
const users = await db.collection('users').where({
_openid: OPENID
}).get()
if (!users.data.length) {
return {
message: 'user not found',
code: 1
}
}
// 进行数据解密
const user = users.data[0]
const wxBizDataCrypt = new WXBizDataCrypt(APPID, user.session_key)
const data = wxBizDataCrypt.decryptData(event.encryptedData, event.iv)
const expireTime = Date.now() + duration
try {
// 将用户数据和手机号码数据更新到该用户数据中
const result = await db.collection('users').where({
_openid: OPENID
}).update({
data: {
...event.user,
phoneNumber: data.phoneNumber,
expireTime
}
})
if (!result.stats.updated) {
return {
message: 'update failure',
code: 1
}
}
} catch (e) {
return {
message: e.message,
code: 1
}
}
return {
message: 'success',
code: 0,
data: {
...event.user,
...data
},
}
}
7.1.5.5. 退出登录¶
如果不需要用户退出登录,单纯依赖 wx.checkSession 就可以作为用户登录态失效的办法。
如果需要允许用户主动退出,请参考以下配置方法。
您可以在用户数据里添加一个 expireTime 字段,用于记录用户登录态失效的时间, 在云函数 user-login-register 里就有 expireTime 的相关配置和写入逻辑。 示例代码如下:
// 节选自 `user-login-register`
const duration = 24 * 3600 * 1000; // 开发侧控制登录态有效时间,此处表时24小时,即1天
// ...... 此处省略其它代码
// 将 expireTime 写入用户数据里
const result = await db.collection('users').where({
_openid: OPENID
}).update({
data: {
...event.user,
phoneNumber: data.phoneNumber,
expireTime
}
})
在 checkUser 方法中,您也可以调用 checkSession 去检测用户数据中的 expireTime 是否过期, 如果过期,则不会再展示用户数据,并更新一下 session_key。
示例代码如下:
// 检查用户是否登录态还没过期
checkSession(expireTime = 0) {
if (Date.now() > expireTime) {
return false;
}
return true;
},
以下此方法,则是用户主动单击退出登录按钮后,触发的方法,会将用户的 expireTime 设零过期。
示例代码如下:
// 退出登录
async bindLogout() {
const userInfo = this.data.userInfo
await this.db.collection('users').doc(userInfo._id).update({
data: {
expireTime: 0
}
})
this.setUserInfo()
}
至此,您已基本完成一个简单有效的用户注册、登录页面。