uni-app development mini program series--WeChat login

What is WeChat login

The official document says: Mini Programs can easily obtain the user identity provided by WeChat through the official login capability provided by WeChat, and quickly establish a user system within the Mini Program. In other words, let the user's user id in our applet be associated with the user's WeChat open_id. In this way, when a user visits, we can get the user's open_id to know the user's id on our platform. In this way, the user can log in to his account without entering the account password.

login process

A sequence diagram is also given in the official documentation of the entire login process, as follows: What needs to be explained is that the developer server is our backend service. Therefore, in order to realize the user's WeChat quick login in the WeChat applet, the cooperation of the front and back ends is required (except for full-stack development). The main process completed on the front-end side is the four steps 1, 2, 6, and 7 in the figure.

Implementation

With the sequence diagram above, it is actually relatively simple for us to complete the login logic, just follow the process. The first step is of course to have a WeChat applet project. The author used the project initialized by uni-app. Friends who have not generated the project can move to my other blog: uni-app development applet series --
project to build

When to trigger user login?

When to trigger user login? Different applications in this place have different requirements and designs. In the author's opinion, because the login process is actually silent, the user is not aware of it, and it does not involve obtaining the user's private data, so it is best to log in immediately to generate a user session state when the user directly accesses it. This allows us to analyze visitor data as well. In the uni-app project, there is a root component App.vue, we can choose to do login logic processing in the onLaunch lifecycle function here. The author's code is as follows:

<script>
export default {
  onLaunch: function() {
    // 从storage获取登录信息,没有则需要登录
    let tokenInfo = uni.getStorageSync("tokenInfo");
    let hasValidToken = false;
    if (tokenInfo) {
      let time = new Date().valueOf();
      // 存储时间小于token失效时间,才是有效token, 否则重新授权
      hasValidToken = time - tokenInfo.timestamp < 3600 * 24 * 1000;
    }
    if (!hasValidToken) {
      // 调用小程序登录api
      uni.login({
        provider: "weixin",
        success: (wxInfo) => {
          // 获取到code后,提交给服务端
          this.$api.post('/wxa/login', {
            code: wxInfo.code,
          }).then((res) => {
            // 存储获取到的token
            uni.setStorage({
              key: 'tokenInfo',
              data: {
                token: res.token,
                timestamp: new Date().valueOf()
              }
            })
          })
        },
      });
    }
  },
  onShow: function() {
    console.log('App Show')
  },
  onHide: function() {
    console.log('App Hide')
  }
}
</script>

The above code already contains steps 1, 2, and 6 in the sequence diagram. There are a few places that need to be explained:

  1. Why use storage?

storage 是官方提供的 api, 用于将数据存储在本地缓存中指定的 key 中。除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。所以我们如果把 token 存储到 storage 里后,可以减少对 wx.login 接口以及后端接口的频繁调用。

  1. 为何要判断 token 是否失效?

笔者的思路是存储 token 时顺便记录下时间戳。获取 token 时,会拿当前时间戳减去存储时的时间戳得到存储时间,然后判断是否超过 30 天(当然这个时间可以和后端协商)。如果超过,则为失效,那么则需要重新去登录。有的人觉得没必要做这一步,如果 token 失效,会在调用其他接口时得到一个类似 401 之类的错误码,然后再去重新登录就行。当然这也是可以的,只是笔者觉得在这里处理的话,逻辑更加集中,且体验相对好一些。

如何携带登录态?

当我们本地获取了 token,则表示登录成功,接下来是在其他请求中携带上 token,这样后端才知道是哪个用户的请求。我们当然不能在每个请求中去加这个逻辑,这样工程量很大。所以,最好先对 wx.request 进行一次封装,然后所有的请求走咱们封装后的方法。笔者代码如下:

// 该处配置为后端接口地址
const defaultHost = "http://api-server.com";

const errorMsg = (response) => {
  let error = {};
  if (response.statusCode) {
    error.code = response.statusCode;
    switch (response.statusCode) {
      case 400:
        error.msg = "错误请求";
        break;
      case 401:
        error.msg = "未授权,请重新登录";
        break;
      case 403:
        error.msg = "拒绝访问";
        break;
      case 404:
        error.msg = "请求错误,未找到该资源";
        break;
      case 405:
        error.msg = "请求方法未允许";
        break;
      case 408:
        error.msg = "请求超时";
        break;
      case 500:
        error.msg = "服务器端出错";
        break;
      case 501:
        error.msg = "网络未实现";
        break;
      case 502:
        error.msg = "网络错误";
        break;
      case 503:
        error.msg = "服务不可用";
        break;
      case 504:
        error.msg = "网络超时";
        break;
      case 505:
        error.msg = "http版本不支持该请求";
        break;
      default:
        error.msg = `连接错误${response.statusCode}`;
    }
  } else {
    error.code = 10010;
    error.msg = "连接到服务器失败";
  }
  return error;
};

function request(path, method, data, setting) {
  const tokenInfo = uni.getStorageSync("tokenInfo");
  const host = setting ? setting.host || defaultHost : defaultHost;
  const token = setting ? setting.token || tokenInfo.token : tokenInfo.token;
  return new Promise((resolve, reject) => {
    uni.request({
      url: host + path,
      method: method,
      data: data,
      header: {
        Authorization: "Bearer " + token,
      },
      success: (res) => {
        // 状态码非200的处理
        if (res.statusCode >= 400) {
          const error = errorMsg(res);
          uni.showToast({
            title: error.msg,
            icon: "none",
          });
          reject(errorMsg(res));
          // errorCallback(errorMsg(res))
        } else if (res.data.code) {
          uni.showToast({
            title: res.data.msg,
            icon: "none",
          });
          // reject(errorMsg(res.data.msg))
          // errorCallback(res.data)
        } else {
          resolve(res.data);
          // successCallback(res.data)
        }
      },
    });
  });
}

export default {
  get: (path, data, otherData) => {
    return request(path, "get", data, otherData);
  },
  post: (path, data, otherData) => {
    return request(path, "post", data, otherData);
  },
  request: request,
};

记住上述代码也有几个地方需要注意:

  1. defaultHost 是后端接口的域名部分,如果是有分测试环境真实环境,那么该处最好根据环境判断一下;
  2. 笔者是按照 jwt 规范在 header 中添加的 token 信息,这里的规则也是可以和后端进行协商;
  3. 关于错误码的判断,笔者判断如果后端返回了自定义的错误码时,返回 0 为正常响应,不为 0 则都算异常。这里需要根据实际情况进行调整。

封装了请求后,我们就可以直接调用需要登录态才能访问的接口了。比如获取用户信息:

<template>
  <div class="my">
    <div>用户ID:{{userInfo.id}}</div>
    <div>用户昵称:{{userInfo.username}}</div>
    <div>用户openId:{{userInfo.openId}}</div>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import request from "@/common/request.js";

  const userInfo = ref({});

  request.get("/user/info").then((res) => {
    userInfo.value = res;
  });
</script>
<style lang="scss" scoped>
  .my {
    width: 100%;
  }
</style>

至此,登录功能完成。

后记

本次任务只是完成了用户登录,如果想获取用户昵称头像甚至手机号,那还需要申请用户授权,比较复杂,笔者将在下篇博客中详细介绍。

关于作者

Graduated from a major in computer science, 8+ years of web development experience, and has been deeply involved in Vue2 and Vue3 technology stacks for many years. With rich experience in full-stack development, the skill tree covers the entire link process from front-end engineering construction to deployment and launch. Keeping up with technology trends, we have been paying attention to various emerging technology trends and actively conducting practical explorations, pursuing elegant development experience, extreme development efficiency, and high-standard development quality.

Welcome to criticize and correct, or communicate with me to discuss front-end technology. The source code has not been made public, please contact me to send it privately.

Contact me: imwty2023 (WeChat)

Blog original address: uni-app development mini program series--WeChat login | imwty

Guess you like

Origin juejin.im/post/7210020384044580925