# 微信小程序云开发实现注册登录 与 多终端常见登录方式

TIP

从本节内容开始,正式进入到微信小程序云开发快速入门和实践应用了,对前面学到的基础知识进行综合实践。

通过前面艾编程-coffee 项目的开发实践,完成并掌握小程序项目的开发对我们后期 Vue 的学习打好基础,同时也是我们未来学习 uni-app 多终端:iOS、Android、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等的必备前提条件。

如下是前面已完成的微信小程序 icoding-coffee 项目(这部分内容没有图文教程,具体代码实践查看直播视频回放即可)

image-20230528204150323

蓝湖UI设计稿

# 一、当前常见的多终端登录方式

TIP

深入浅出市面上常见的多终端登录方式,账号、密码登录,手机验证码登录,第三方登录,APP 扫码登录,身份证、人脸识别登录,包括本节我们会学习的微信小程序的登录等

# 1、账号、密码登录

TIP

以艾编程 PC 端官网为例,需要先注册一个账号 -> 在登录窗口输入用户名 和 密码 -> 点击登录按钮,登录成功

image-20230525170142645

账号、密码登录方式的特点

  • 输入麻烦
  • 不便于记忆

该方式已经开始淡出市场,很多平台已经开始降低 账号、密码登录方式展示的优先级。有些终端就直接去掉了 账号、密码登录的方式。

# 2、手机验证码登录

TIP

第二种比较常见的通过手机验证码的方式登录

image-20230525170658146

手机验证码登录的特点

  • 麻烦度适中
  • 无需记忆(自己的手机号正常不会忘记)
  • 跨平台方便
  • 必备的登录方式之一

# 3、第三方登录

TIP

目前很多应用不论是 PC 端 还是 移动端 都会提供第三方登录(微信、QQ、微博、支付宝等),只需通过点击对应的登录渠道来使用第三方 APP 授权当前应用即可

这种方式的好处:可以直接使用已有的账号体系来授权即可

image-20230525171944729

第三方登录的特点

  • 扫一扫、唤起 APP 授权登录
  • 无需记忆
  • 必备的登录方式之一

# 4、APP 扫码登录

TIP

APP 扫码登录仅限于同时使用 PC 端 和 移动 APP 端的用户,在手机上打开 APP 应用完成登录 -> 使用 APP 中扫一扫功能、授权 -> 完成 PC 端的快捷登录

image-20230525172738842

APP 扫码登录的特点

  • 扫一扫授权登录(必须保证 APP 上已经完成登录的情况下)
  • 无需记忆
  • 只会在 PC 端 上才会有

# 5、身份证、人脸识别登录

TIP

在金融 或 政务类的应用中会经常使用 身份证、人脸识别登录,在实际中通常会将这两者结合来使用

image-20230525174357728

身份证、人脸识别登录的特点

  • 金融、政务
  • 无需记忆
  • 主要应用在移动端上 或 一些专用的设备来实现(本质需要具备摄像头)

当然,还有指纹识别、虹膜等登录方式

# 6、登录的目的

TIP

在千行百业中,有各种各样的登录方式,不论使用哪一种方式来实现登录最终的实现目的都是一样的。

登录的目的: 在对应的系统中建立当前用户的唯一标识,即 用户 ID

注:

一个系统是可以被多个用户使用的,每个用户应该都是唯一的,并且每个用户都会在平台上产生不同的数据。我们需要区分不同用户产生的数据。

登录方式的多样化,只是社会发展 与 科技进步,使用习惯改变的必然产物,但它的目的一直没有变过。

那么,在小程序中它的登录方式又是怎样的呢 ?

# 二、小程序登录

TIP

在小程序中用户登录的方式有两种

  • 借助微信小程序生态的能力,获取当前手机号 或 让用户输入手机号、再获取手机验证码的方式
  • 通过调用小程序的原生 API ,wx.login(Object object) (opens new window) 来实现登录,即当前用户在此小程序内的唯一标识:openId

详细查阅,微信小程序官方文档 - 登录流程时序 (opens new window)

img

# 1、前后端分离架构登录流程

TIP

在前后端分离架构下,大致的登录流程都一样,以下为主流登录的实现的流程

image-20230525184340135

注:

  • 在客户端提供登录信息,即:在浏览器、APP 上做登录操作
  • 将信息提交给服务端做判断
  • 建立唯一标识,本质上就是生成一个用户 ID
  • 服务端会经过一系列的逻辑运算,最终生成一个 令牌(一串加密的字符串)
  • 再将该令牌返回给客户端

其中,服务端判断、建立唯一标识的过程会根据业务的复杂度 与 安全要求的不同,每一个公司的具体实现流程不一定一样。作为前端开发也并不需要这块是如何实现的,本身是由服务端开发的同学负责的。

令牌

即一段经过加密的字符串。具体表现:不能一眼从该字符串中提取出有用的信息

通过调用一个登录的接口,传递一些私有的信息,服务端就会给我们返回一个令牌,这个过程就是登录的过程

令牌的用途:

客户端在接收到令牌后,通常是需要保存令牌的,然后在所有需要身份鉴权的接口调用中附带这个令牌,服务端相关鉴权的模块就会去解密这个令牌(加密字符串)。

拿到解密后的身份信息,接着来判断当前的请求是否合法。这也是目前很常见的一种接口鉴权的流程

# 2、个人中心页面结构

TIP

  • 页面结构的编写
  • 登录前 和 登录后的页面结构
  • 登录前后的条件控制

pages/me/me.wxml

<!--pages/me/me.wxml-->
<view class="container">
  <view class="users">
    <!-- 未授权登录状态 -->
    <view class="userinfo" wx:if="{{!userInfo}}">
      <image
        src="https://web.arryblog.com/applets-img/default-avatar.png"
        mode="cover"
        class="user-avatar"
      />
      <text class="user-desc">登录获取更多会员权益</text>
      <button class="btn-login" bindtap="handleLogin">授权登录</button>
    </view>
    <!-- 授权登录状态 -->
    <view class="userinfo" wx:else>
      <view class="user-login-suc">
        <image src="{{userInfo.avatarUrl}}" mode="cover" class="user-avatar" />
        <text class="user-login-desc">Hi {{userInfo.nickName}} ~ </text>
      </view>
      <button class="btn-login" bindtap="outLogin">退出登录</button>
    </view>
  </view>
  <view class="nav-list">
    <!-- 登录后显示:个人订单 和 收货地址,否则不显示 -->
    <block wx:if="{{userInfo}}">
      <view class="item-list">
        <view class="item-wrap">
          <i-icon name="dingdan1" size="33" color="#FF5A62" />
          <text class="icon-text">个人订单</text>
        </view>
        <i-icon name="right-line" size="33" />
      </view>
      <view class="item-list">
        <view class="item-wrap">
          <i-icon name="didian_dingwei" size="33" color="#FF5A62" />
          <text class="icon-text">收货地址</text>
        </view>
        <i-icon name="right-line" size="33" />
      </view>
    </block>
    <view class="item-list">
      <view class="item-wrap">
        <i-icon name="gonggao_tongzhi" size="33" color="#FF5A62" />
        <text class="icon-text">系统消息</text>
      </view>
      <i-icon name="right-line" size="33" />
    </view>
    <view class="item-list">
      <view class="item-wrap">
        <i-icon name="kefu" size="33" color="#FF5A62" />
        <!-- <text class="icon-text">在线客服</text> -->
        <button open-type="contact" class="btn-text">在线客服</button>
      </view>
      <i-icon name="right-line" size="33" />
    </view>
    <view class="item-list">
      <view class="item-wrap">
        <i-icon name="zhengzaijinhang" size="33" color="#FF5A62" />
        <!-- <text class="icon-text">意见反馈</text> -->
        <button open-type="feedback" class="btn-text">意见反馈</button>
      </view>
      <i-icon name="right-line" size="33" />
    </view>
  </view>
  <view class="adv-con">
    <image
      src="https://web.arryblog.com/applets-img/me-adv-banner-1.png"
      mode="cover"
      class="me-adv-img"
    />
  </view>
</view>

# 3、个人中心页面样式

pages/me/me.wxss

/* pages/me/me.wxss */
.container {
  padding: 28rpx;
}
.users {
  width: 100%;
  height: 249rpx;
  background-image: url(https://web.arryblog.com/applets-img/me-bgc-banner.png);
  background-size: cover;
}
.userinfo {
  height: 100%;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 0 20rpx;
  align-items: center;
}
.user-login-suc {
  display: flex;
  flex-direction: row;
  align-items: center;
  color: #fff;
}
.user-login-desc {
  margin-left: 20rpx;
}
.user-desc {
  color: #fff;
}
.user-avatar {
  width: 128rpx;
  height: 128rpx;
  border-radius: 50%;
}
.btn-login:not([size="mini"]) {
  width: 150rpx;
  height: 60rpx;
  font-weight: 400;
  font-size: 26rpx;
  padding: 0;
  margin: 0;
  line-height: 60rpx;
  background-color: #ffca8c;
  color: #fff;
}
.item-list {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  line-height: 116rpx;
  border-bottom: 1px solid #f6f6f6;
}
.icon-text {
  margin-left: 30rpx;
}
.icon-right-line {
  color: #cacad6;
}
.adv-con {
  text-align: center;
  margin-top: 20rpx;
}
.me-adv-img {
  width: 623rpx;
  height: 177rpx;
}

.item-wrap {
  display: flex;
  align-items: center;
}

/* 去掉按钮的默认样式 */
.btn-text:not([size="mini"]) {
  width: 100%;
  background-color: #fff;
  border: none;
  text-align: left;
  margin: 0;
  padding: 0;
  line-height: 1.3;
  font-size: 28rpx;
  font-weight: 400;
  margin-left: 30rpx;
}
/* 去掉边框 */
.btn-text::after {
  border: none;
  border-radius: 0;
}

# 4、个人中心逻辑处理

TIP

  • 用户授权信息,使用 wx.getUserProfile() (opens new window) API 接口获取用户的头像昵称信息
  • 将用户头像和昵称存储在 本地存储中,当用户再次进入小程序时,不必重复授权登录
  • 退出登录,清空本地存储数据 同时 清空页面数据

pages/me/me.js

// pages/me/me.js

// 获取应用的实例
const app = getApp();

Page({
  data: {
    userInfo: {},
  },

  // 授权登录
  async handleLogin() {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗

    // 获取用户信息
    const res = await wx.getUserProfile({
      desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
    });

    // 实现用户注册(如果用户已注册,直接登录即可)
    // 设置加载中的提示
    // wx.showLoading({
    //  title: '正在授权中 ...',
    // })

    // 使用微信小程序云开发 或 对接我们的后端注册登录 API 接口

    // 授权成功后
    // 将用户信息存储在本地存储中,用于用户下次进入小程序时,不必重复授权登录
    await app.setStorage("userInfo", res.userInfo);

    // 更新 data
    this.setData({
      userInfo: res.userInfo,
    });
  },

  // 退出登录
  outLogin() {
    // 清除本地缓存
    app.rmStorage("userInfo");
    // 更新 data
    this.setData({
      // 清空页面数据
      userInfo: "",
    });
  },

  // 生命周期函数 - 页面加载时执行
  async onLoad() {
    // 获取本地缓存中的用户登录信息
    const userInfo = await app.getStorage("userInfo");
    // 更新data
    this.setData({
      userInfo,
    });
  },
});

# 三、微信小程序云开发快速入门

TIP

快速了解微信小程序云开发常见基础概念,掌握登录注册必备的基础

# 1、企业项目开发到上线的环境配置

TIP

一般企业项目开发到上线的几个阶段:开发 -> 测试 -> 预发布 -> 生产,其中生产环境也就是通常说的真实线上环境。(根据企业和项目规模的不同会有不同,大致流程都一样)

# 1.1、dev(开发环境)

TIP

DEV (Development Environment)开发环境,用于软件开发人员开发联调时使用。

即:开发同学开发时使用的环境,就是与测试环境分开的独立客户机、服务器、配置管理工具等。开发同学专门用于开发及调试的服务器,配置可以比较随意, 为了开发调试方便,打开错误报告方便调试。通常表示最低环境,由代码开发人员直接使用和维护,是代码最超前版本的一个环境。

每位开发同学在自己的 dev 分支上干活,提测前或者开发到一定程度,各位同学会合并代码,进行联调。

# 1.2、test 或 FAT(测试环境)

TIP

FAT (Feature Acceptance Test Environment)功能验收测试环境,也就是俗称的测试环境。

测试同学干活的环境,测试人员利用一些工具及数据所模拟出的、接近真实用户使用环境的环境。 目的:为了使测试结果更加真实有效。

测试环境应该与开发环境分隔开,使用独立的客户机、服务器和配置管理工具。是测试者测试及改 bug 的环境;这个环境要和生产环境类似。

开发人员确认代码分支在开发环境自测没有问题后,提交测试环境进行测试。测试环境对代码和系统已经集成,可以供测试人员进行功能模块测试,集成测试,系统测试,测试环境有独立的数据库和账号权限管理系统,由测试人员使用和管理,功能型 bug 一般在测试环境中暴露较多。

# 1.3、uat(预发布环境)

TIP

UAT (User Acceptance Test Environment)用户验收测试环境,类似于预发布环境。

预发布环境是正式发布前最后一次测试,所有的功能和配置,数据库都已经与线上环境高度相似,仅准入本次需要上线的功能代码,测试人员确认代码在测试环境经过测试用例测试没有问题后,提交预发布环境进行测试。因为在少数情况下即使预发布通过了,都不能保证正式生产环境可以 100%不出问题;

预发布环境的配置,数据库等都是跟线上一样,有些公司的预发布环境数据库是连接线上环境,有些公司预发布环境是单独的数据库;如果不设预发布环境,如果开发合并代码有问题,会直接将问题发布到线上,增加维护的成本;

预发布环境和测试环境的区别:

预生产环境和生产系统的同步性更高,几乎一样,有些测试,比如需要大数据量的,用预生产环境看程序性能比用测试环境(一般情况下数据会较少)会更准确。

注:

预发布环境一般会连接生产环境的数据库(看企业和项目规模不同),测试时要注意,以免产生脏数据,避免影响生产环境的使用

# 1.4、prod(生产环境)

TIP

PRO (Production Environment)生产环境

即发布正式线上环境,真实用户访问的环境(最高环境,直接面向真实的用户)。由特定人员来维护,一般人没有权限去修改。该环境是指正式提供对外服务的,真实用户线上使用的环境;关闭错误报告,打开错误日志。

预发布环境和生产环境区别:

  • 预发环境中新功能为最新代码,其他功能代码和生产环境一致。
  • 预发环境和生产环境的访问域名不同。

# 1.5、灰度发布

TIP

灰度发布,发生在预发布环境之后,生产环境之前。

生产环境一般会部署在多台机器上(跨地域、跨机房部署),以防某地域、某机房、某台机器出现故障,这样其他机器可以继续运行,不影响用户使用。灰度发布会发布到其中的几台机器上,验证新功能是否正常。如果失败,只需回滚这几台机器即可。

# 2、创建小程序云开发环境

TIP

在微信开发中工具左上角的工具栏中,点击云开发按钮,进入 “创建云开发环境”

image-20230526153131132

注:

点击确认后,需要等待一点时间,即可创建成功。

学习阶段,创建好云开发环境后不要随意删除,删除后再创建就需要付费使用了,免费额度只有一次机会。

创建成功后,会进入如下面板

image-20230526163154030

# 3、常用的云开发服务

TIP

使用微信小程序云开发常用的云开发服务有:云函数、数据库、存储

# 3.1、云函数

TIP

云函数 (opens new window)是一段运行在云端的代码,无需管理服务器,在开发工具内编写、一键上传部署即可运行后端代码。

小程序内提供了专门用于云函数调用的 API。开发者可以在云函数内使用 wx-server-sdk (opens new window) 提供的 getWXContext (opens new window) 方法获取到每次调用的上下文(appidopenid 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid)。

详细查阅,微信小程序官方文档 - 云函数 (opens new window)

# 3.2、数据库

TIP

云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。

详细查阅,微信小程序官方文档 - 数据库 (opens new window)

# 3.3、存储

TIP

云开发提供了一块存储空间,提供了上传文件到云端、带权限管理的云端下载能力,开发者可以在小程序端和云函数端通过 API 使用云存储功能。

在小程序端可以分别调用 wx.cloud.uploadFilewx.cloud.downloadFile 完成上传和下载云文件操作。

详细查阅,微信小程序官方文档 - 存储 (opens new window)

# 四、使用微信小程序云开发实现注册登录

TIP

深入浅出微信小程序云开发的具体实践与应用。

# 1、创建云开发环境

TIP

在项目根目录 project.config.json 项目配置文件中,初始化配置云函数的文件夹的目录

{
  "cloudfunctionRoot": "cloud/"
}

在项目根目录中,新建 cloud 文件夹

image-20230526171013771

选择项目对应的环境

image-20230526171213622

# 2、注册用户账号

TIP

当点击登录按钮后,通过微信的开发能力获取了微信昵称和头像基础信息后,将这些基础信息或其他信息一并提交给后端,用于创建一个用户账号(注册)。

但这两个字段不能作为用户的唯一标识 !(微信昵称是会有重复的不能作为唯一标识)

注:

注册用户账号的同时,在小程序中就需要一个唯一字段 openId 来标识(伴随着微信账户本身,类似当前用户的身份证 ID 或 一样),既不会重复也不会失效。

这就是微信小程序中的鉴权机制,即 通过 openId 来鉴权。详细查阅,微信小程序官方文档 - 小程序登录 (opens new window) 流程时序图可以看到

# 2.1、新建数据库表(集合)

TIP

在云开发控制台中,创建数据表 users 用于存储用户信息

image-20230526191738515

# 2.2、新建登录云函数

TIP

cloud 文件夹上右键 -> 选择 ”新建 Node.js 云函数“

image-20230526192212487

输入云函数名称 login 即可新建成功

image-20230526192526889

cloud/login/index.js 中编写云函数的逻辑

// 云函数入口文件
const cloud = require("wx-server-sdk");

// 1、指定云函数的环境,注:使用云函数前必须指定环境进行初始化
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境

// 云函数入口函数
// event 指:前端传给后端的字段
exports.main = async (event) => {
  const wxContext = cloud.getWXContext();

  return {
    event,
    // 微信的唯一标识符,返回给前端
    openid: wxContext.OPENID,
  };
};

# 2.3、在微信端调用云函数

pages/me/me.js 中调用云函数

// pages/me/me.js

// 获取应用的实例
const app = getApp();

Page({
  // 页面的初始数据
  data: {
    userInfo: {},
    hasUserInfo: false,
  },

  // 授权登录
  async handleLogin(e) {
    // 1、获取用户信息
    const { userInfo } = await wx
      .getUserProfile({
        desc: "desc",
      })
      .catch((err) => {
        console.log(err);
      });

    if (userInfo) {
      // 授权成功后
      // 将用户信息存储在 本地存储中,用于用户下次进入小程序时,不必重复授权登录
      app.setStorage("userInfo", userInfo);

      // 实现用户注册(如果用户已注册,直接登录即可)
      // 设置加载中的提示
      wx.showLoading({
        title: "正在授权中 ...",
      });

      // 调用云函数 -------------------
      // 2、把当前用户信息提交给后端,用于创建生成一个用户账号(即:注册用户信息
      wx.cloud.callFunction({
        name: "login",
        data: {
          avatarUrl: userInfo.avatarUrl,
          nickName: userInfo.nickName,
        },
      });

      // 更新 data
      this.setData({
        userInfo,
        hasUserInfo: true,
      });
    }
  },

  // 省略部分代码 ...
});

注:

当点击登录后,控制台会报错 “请先调用 wx.cloud.init() 完成初始化后再调用其他云 API。”

image-20230527154735854

# 2.4、在小程序 app.js 中初始化云函数

// app.js

import { Storage } from "./utils/storage";

const storage = new Storage();

App({
  setStorage: storage.set,
  getStorage: storage.get,
  rmStorage: storage.rm,
  // 小程序初始化完成时触发,全局只触发一次。
  onLaunch() {
    this.getStorage("cartInfo")
      .then((res) => {
        // 设置 tarBar 图标
        res.length > 0
          ? wx.setTabBarBadge({
              index: 3,
              text: String(res.length),
            })
          : wx.removeTabBarBadge({
              index: 3,
            });
      })
      .catch((err) => {
        console.log(err);
      });

    // 小程序端初始化 --------------
    wx.cloud.init({
      // API 调用的默认环境配置
      env: "icoding-coffee-dev-5dontd2874c31",
      // 将用户访问记录到用户管理中,在控制台中可见
      traceUser: true,
    });
  },
});

注:

以上 env 环境 ID 需要在云开发控制台中 复制自己的 环境 ID 即可。

此次,再次点击登录测试,后端就返回了我们需要的 openId 和 我们前端传过去的用户信息了。

image-20230527155550303

# 2.5、注册用户信息 至 数据库中

TIP

当我们拿到了注册用户所需要的信息后,就开始正式注册用户信息了,即往数据库中插入一条用户数据

注:

  • 如果数据库中存在当前用户的信息(已注册过),则直接返回当前用户信息(登录)
  • 如果数据库中没有当前用户的信息(注册)

先完成用户信息的注册,如下

cloud/login/index.js 云函数中完成用户信息的注册,即往数据库中插入一条数据

// 云函数入口文件
const cloud = require("wx-server-sdk");

// 1、指定云函数的环境,注:使用云函数前必须指定环境进行初始化
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境

// 云函数入口函数
// event 指:前端传给后端的字段
exports.main = async (event) => {
  const wxContext = cloud.getWXContext();
  // 解构前端传入的字段
  const { avatarUrl, nickName } = event;

  // 如果数据库中没有当前用户的信息(注册)

  // 1、初始化数据库(获取数据库的引用)
  const db = cloud.database();
  // 2、指定集合(数据表),获取集合的引用
  const users = db.collection("users");
  // 3、在数据库中新增用户数据(注册)
  const data = await users.add({
    data: {
      avatarUrl,
      nickName,
      // 注册时所需要的其他字段信息也可以再次加入 ...

      // 账户余额
      money: 0,

      // 注册时一定要加入 openid 作为唯一标识
      _openid: wxContext.OPENID,
    },
  });

  // 添加数据库信息后,返回值(即:当前用户信息的 ID)
  return {
    data,
  };
};

注:

编译运行后,点击登录按钮,发现 NetWork 发送请求 和 服务器返回的数据并没有发生变化。同时,数据库中也并没有插入用户信息 !

原因是:我们重新修改了云函数后,并没有上传,因此不会生效

image-20230527163058861

等待上传成功后,即可正常运行,并插入数据库成功

image-20230527164814794

查看云数据库中 users 集合中添加成功的数据

image-20230527165108824

# 3、实现登录逻辑

TIP

当我们再次,点击登录时,就会重复注册当前用户的信息。并没有走登录逻辑,因此,我们需要来处理用户重复注册的问题。

思路: 如果数据库中存在当前用户的信息(已注册过),则直接返回当前用户信息(登录)

cloud/login/index.js 中编写根据 openId 查询用户信息的方法

// 云函数入口文件
const cloud = require("wx-server-sdk");

// 1、指定云函数的环境,注:使用云函数前必须指定环境进行初始化
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境

// 云函数入口函数
// event 指:前端传给后端的字段
exports.main = async (event) => {
  const wxContext = cloud.getWXContext();
  // 解构前端传入的字段
  const { avatarUrl, nickName } = event;

  // 如果数据库中没有当前用户的信息(注册)

  // 1、初始化数据库(获取数据库的引用)
  const db = cloud.database();
  // 2、指定集合(数据表),获取集合的引用
  const users = db.collection("users");

  // 在新增用户数据之前
  // 3、先查询当前用户是否已注册过
  const { data } = await users
    .where({
      _openId: wxContext.OPENID,
    })
    .get();

  // 3、在数据库中新增用户数据(注册)
  // const data = await users.add({
  //   data: {
  //     avatarUrl,
  //     nickName,
  //     // 注册时所需要的其他字段信息也可以再次加入 ...

  //     // 账户余额
  //     money: 0,

  //     // 注册时一定要加入 openid 作为唯一标识
  //     _openId: wxContext.OPENID
  //   }
  // })

  // 添加数据库信息后,返当前用户信息的 ID
  return {
    data,
  };
};

注:

当数据库中存在该用户信息时,查询结果返回一个 数组(当前用户的信息)

如果没有查询到该用户数据时,数组即为空

image-20230527171728396

# 3.1、登录注册逻辑判断

TIP

如果数据库中存在当前用户的信息(已注册过),则直接返回当前用户信息(登录),如果数据库中没有当前用户的信息(注册)

即可通过以上根据 openId 查询返回的数组的长度来进行判断,是否已存在该用户

cloud/login/index.js 中修改逻辑

// 云函数入口文件
const cloud = require("wx-server-sdk");

// 1、指定云函数的环境,注:使用云函数前必须指定环境进行初始化
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境

// 云函数入口函数
// event 指:前端传给后端的字段
exports.main = async (event) => {
  const wxContext = cloud.getWXContext();
  // 解构前端传入的字段
  const { avatarUrl, nickName } = event;

  // 如果数据库中没有当前用户的信息(注册)

  // 1、初始化数据库(获取数据库的引用)
  const db = cloud.database();
  // 2、指定集合(数据表),获取集合的引用
  const users = db.collection("users");

  // 在新增用户数据之前
  // 3、先查询当前用户是否已注册过
  const { data } = await users
    .where({
      _openId: wxContext.OPENID,
    })
    .get();

  // 根据返回的 data 数组的长度来进行判断,是否已存在该用户
  if (data.length === 0) {
    // 等于 0 ,数据库中没有当前用户的信息(注册)

    // 在数据库中新增用户数据(注册)
    const res = await users.add({
      data: {
        avatarUrl,
        nickName,
        // 注册时所需要的其他字段信息也可以再次加入 ...

        // 账户余额
        money: 0,

        // 注册时一定要加入 openid 作为唯一标识
        _openId: wxContext.OPENID,
      },
    });

    // 注册成功后,返回信息
    return {
      res,
    };
  } else {
    // 如果数据库中存在当前用户的信息(已注册过),则直接返回当前用户信息(登录)
    return {
      data: data[0],
    };
  }
};

注:

更新完逻辑后,重新上传并部署云函数,再次测试

可以正常新增用户 和 登录用户了,登录成功后会成功返回用户信息。但注册成功后,后端只会返回了当前的用户 ID,这里就不对了。注册成功后同样需要返回当前用户信息(用于页面的展示)

# 3.2、优化注册成功后,返回用户信息

TIP

当用户注册成功后,根据 ID 快速查询当前用户的信息,并返回给前端

cloud/login/index.js 中修改逻辑

// 云函数入口文件
const cloud = require("wx-server-sdk");

// 1、指定云函数的环境,注:使用云函数前必须指定环境进行初始化
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境

// 云函数入口函数
// event 指:前端传给后端的字段
exports.main = async (event) => {
  const wxContext = cloud.getWXContext();
  // 解构前端传入的字段
  const { avatarUrl, nickName } = event;

  // 如果数据库中没有当前用户的信息(注册)

  // 1、初始化数据库(获取数据库的引用)
  const db = cloud.database();
  // 2、指定集合(数据表),获取集合的引用
  const users = db.collection("users");

  // 在新增用户数据之前
  // 3、先查询当前用户是否已注册过
  const { data } = await users
    .where({
      _openId: wxContext.OPENID,
    })
    .get();

  // 根据返回的 data 数组的长度来进行判断,是否已存在该用户
  if (data.length === 0) {
    // 等于 0 ,数据库中没有当前用户的信息(注册)

    // 在数据库中新增用户数据(注册)
    const res = await users.add({
      data: {
        avatarUrl,
        nickName,
        // 注册时所需要的其他字段信息也可以再次加入 ...

        // 账户余额
        money: 0,

        // 注册时一定要加入 openid 作为唯一标识
        _openId: wxContext.OPENID,
      },
    });

    // ---------------------
    // 当用户注册成功后,根据 ID 快速查询当前用户的信息,并返回给前端
    // doc:接收 _id 快速返回该 id 的数据
    const user = await users.doc(res._id).get();

    // 注册成功后,返回当前用户数据
    return {
      data: user.data,
    };
  } else {
    // 如果数据库中存在当前用户的信息(已注册过),则直接返回当前用户信息(登录)
    // 登录成功后,返回当前用户数据
    return {
      data: data[0],
    };
  }
};

# 4、在小程序页面逻辑获取登录后的数据

TIP

  • 在页面逻辑中获取登录后的用户数据
  • 并将数据库中真实的用户信息同步到本地存储中,做数据的持久化

pages/me/me.js

// pages/me/me.js

// 获取应用的实例
const app = getApp();

Page({
  // 页面的初始数据
  data: {
    userInfo: {},
    hasUserInfo: false,
  },

  // 授权登录
  async handleLogin(e) {
    // 1、获取用户信息
    const { userInfo } = await wx
      .getUserProfile({
        desc: "desc",
      })
      .catch((err) => {
        console.log(err);
      });

    if (userInfo) {
      // 实现用户注册(如果用户已注册,直接登录即可)
      // 设置加载中的提示
      // wx.showLoading({
      //   title: '正在授权中 ...',
      // })

      // 调用云函数
      // 2、把当前用户信息提交给后端,用于创建生成一个用户账号(即:注册用户信息
      // 也可以使用双重解构 将 res 替换成  {result:{data}}
      const res = await wx.cloud.callFunction({
        name: "login",
        data: {
          avatarUrl: userInfo.avatarUrl,
          nickName: userInfo.nickName,
        },
      });

      // console.log(res)

      // 授权成功后
      // 将用户信息存储在 本地存储中,用于用户下次进入小程序时,不必重复授权登录
      app.setStorage("userInfo", res.result.data);

      // 更新 data
      this.setData({
        // 更新 userInfo 的数据
        userInfo: res.result.data,
        hasUserInfo: true,
      });
    }
  },

  // 省略部分代码 ...
});

# 5、当页面切换时,实时获取最新的用户数据

pages/me/me.js

// 生命周期函数--监听页面显示
async onShow() {

    // 获取本地存储中的用户信息
    const res = await app.getStorage('userInfo')
    .catch(err => {
        console.log(err)
    })

    if(res){

        // 如果 res 中有值,表示用户已经登录了
        // 此时,需要请求当前用户数据库中的最新数据(与数据库信息保持同步)
        const user = await wx.cloud.database().collection('users').doc(res._id).get()

        console.log(user)

        // 更新data
        this.setData({
            userInfo: user.data,
            hasUserInfo: true
        })
    }
},

当页面切换时,控制台会报错,是因为数据库没有用户可读的权限

image-20230527200048635

在云开发控制台中,修改数据库的权限即可

image-20230527195702319

心语:

专注,就是将精力集中在某一个领域,消耗的少,聚焦的多,就容易在一个行业里站住脚,再积累几年,就生长出来了。

要紧的是果敢的迈出第一步,对与错都先不管,自古就没有设计好再开步的事儿,别想把一切都弄清楚再去走路。鲁莽者要学会思考,善思者要克服的是犹豫。目的可求完美,举步之际则无需周全。

生活不能等待别人来安排,要自己去争取和奋斗。不要质疑你现在写的一行行代码,做的一道道题,开发一个个项目,它终要把你送向更远的远方,你的使命是去看世界,成为更好的自己,这些都是替你打开世界的方式。

上次更新时间: 6/8/2023, 9:23:17 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X