用户管理 =========== 功能概述 ----------- 本文档主要介绍用户管理功能,您可以通过本文示例体验用户注册、登录、退出登录功能。 体验功能 ----------- .. image:: https://main.qcloudimg.com/raw/c3bffdddfa54ecd95b5a569b10f71840.png 体验 DEMO ----------- 本章的案例代码,请参考 `tcb-demo-basic `_ 。 DEMO 接入流程 --------------- 代码下载完成后,请按照以下步骤操作: 1. 在云开发中创建好至少一个环境。 2. 在 app.js 文件中,如果使用默认环境,则没有改动。 如果需要使用非默认环境,则需要在 wx.cloud.init 中传入环境 ID。 如果使用非默认环境,在云函数的 cloud.init 处,也需要补充环境 ID。 如下图所示: .. image:: https://main.qcloudimg.com/raw/69d8d7590284cc80776021f3f8883948.png 示例代码如下: .. code:: js wx.cloud.init({ env: 'xxxx', // 环境 id traceUser: true }); 3. 在云函数根目录 cloud/functions 中找到函数 user-login-register 和 user-session, 在两者的 config 目录中新建 index.js,填入小程序的密钥 AppSecret,用 npm 安装两个云函数的依赖,并上传两个云函数。 .. image:: https://main.qcloudimg.com/raw/532124c3661e43c86cddca24a85ad508.png 4. 在云开发的数据库中,新建 collection,名为 users。 源码介绍 ----------- 准备工作 ~~~~~~~~~~ 用户管理,包括用户的信息(昵称、性别、头像等的获取)、注册、登录、鉴权等。 本节主要介绍云开发如何操作用户管理。 开发前,建议先阅读以下相关的文档:微信登录能力优化 和 获取用户信息。 参考常用的小程序,例如知乎大学、百果园、摩拜等。 .. image:: https://main.qcloudimg.com/raw/085b0eee6a5533f80c4708eb29ad7c98.png 基本的流程: - 用户授权小程序可获取用户的开放数据。 - 选择登录方式(微信绑定的手机/用户的其它手机)。 - 如果选用了微信绑定的手机,直接信任,注册/登录成功,而如果选用其它手机,则还需要通过发送短信进行手机验证。 用户登录、注册与信息 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 用户的登录、注册、获取信息过程,涉及到以下接口。 详情请参考 用户信息 文档。 - wx.getSetting,看看用户有没有授权小程序,可以获取昵称、头像、性别等的用户信息。 - wx.getUserInfo (旧版) / button (新版),授权后,可通过此接口/组件获取用户信息。 - wx.checkSession,获取 session_key 后,要检查 session_key 是否过期。 - 如果 session_key 过期,则通过 wx.login 获取 code 后,在云函数中调用 code2Session 更新 session_key。 - 通过 button 组件,获取手机号码加密数据,并在云函数中通过之前取得的 session_key 进行解密获取真实手机号码。 流程图如下所示: .. image:: https://main.qcloudimg.com/raw/e2cd0d8be1e4bb3809ce1eedf8d4f820.png 用户授权 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 对于小程序来说,必须进行用户授权,才能在后续中获取用户的开放数据。 因此,我们在 onLoad 生命周期里,做了以下处理: - **旧版:** 做授权的检测。 - **新版:** 在模板文件中,则设置授权按钮。 授权后,请将用户数据存入临时的对象中,示例代码如下: .. code:: js 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) }, .. code:: html 数据解密 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 由于某些数据的安全性问题,需要在后台服务进行解密,譬如手机号码,我们可以借助云开发的云函数去完成。 通过 checkUser 方法,检测 session_key 是否已经过期;如果过期,则重新设置,并将用户写入数据库中。 此逻辑通过 updateSession 方法和云函数 user-session 共同完成。 示例代码如下: .. code:: js // 检测小程序的 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 与用户的数据关联,为后续进行数据解密打下了基础。 .. code:: js // 云函数入口函数 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。 .. code:: js 单击【微信快速登录】后,请调用 bindGetPhoneNumber,将存放于临时对象的用户开放数据, 以及加密的微信手机数据,发送到 user-login-register 进行解密,并存入用户的数据中。 .. code:: js // 获取用户手机号码 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' }) } }, 详细解密数据的原理,请参考 `开放数据校验与解密 `_ 。 .. code:: js 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 }, } } 退出登录 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 如果不需要用户退出登录,单纯依赖 wx.checkSession 就可以作为用户登录态失效的办法。 - 如果需要允许用户主动退出,请参考以下配置方法。 您可以在用户数据里添加一个 expireTime 字段,用于记录用户登录态失效的时间, 在云函数 user-login-register 里就有 expireTime 的相关配置和写入逻辑。 示例代码如下: .. code:: js // 节选自 `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。 示例代码如下: .. code:: js // 检查用户是否登录态还没过期 checkSession(expireTime = 0) { if (Date.now() > expireTime) { return false; } return true; }, 以下此方法,则是用户主动单击退出登录按钮后,触发的方法,会将用户的 expireTime 设零过期。 示例代码如下: .. code:: js // 退出登录 async bindLogout() { const userInfo = this.data.userInfo await this.db.collection('users').doc(userInfo._id).update({ data: { expireTime: 0 } }) this.setUserInfo() } 至此,您已基本完成一个简单有效的用户注册、登录页面。