Home  >  Article  >  WeChat Applet  >  A brief analysis of how to implement the login function in mini programs

A brief analysis of how to implement the login function in mini programs

青灯夜游
青灯夜游forward
2021-12-06 10:11:354258browse

How to implement the login function in the mini program? This article will introduce to you the correct way to open the mini program login. I hope it will be helpful to you!

A brief analysis of how to implement the login function in mini programs

Mini Program Network Component

https://developers.weixin.qq.com/miniprogram /dev/api/network/request/wx.request.html

RequestTask Description

##Method DescriptionAbort the request task. Listen to HTTP Response Header events. will occur earlier than the request completion event. Cancel listening for HTTP Response Header events. Listen to the Transfer-Encoding Chunk Received event. Fired when a new chunk is received. Cancel listening for the Transfer-Encoding Chunk Received event.
RequestTask.abort()
RequestTask.onHeadersReceived(function callback)
RequestTask.offHeadersReceived(function callback)
RequestTask.onChunkReceived(function callback)
RequestTask.offChunkReceived(function callback)

wx.request(Object object) attribute

Only the more commonly used attributes are listed here, all attributes please See Link.

AttributeTypeDefault valueRequiredDescriptionurlstringdataheaderSet the requested header, in the header Referer cannot be set. Default is timeoutnumberTimeout in millisecondsmethodstringGETNoHTTP request methodsuccessfunctionCallback function for successful interface callfailfunctionCallback function for interface call failurecompletefunction##NoTo summarize: all mini program interfaces basically have two characteristics:

## is the
developer server Interface address
string/object/ArrayBuffer
No
Requested Parameter
Object ##No
content-typeapplication/json
No
No
No

Interface The callback function that ends the call (will be executed if the call is successful or failed) even if it is an aborted request!

Parameters It's all an object. Easy to remember and easy to expand.

  • all have the same result processing method: they all have three callback attributes: success, fail, and complete.

  • Introduction to the errMsg object in various situations when the interface is executed.

Callback attributeerrMsg objectsuccess{errMsg:"request:ok"...}{errMsg:"request:fail "...} Some systems have this There is a space after fail, so to use this judgment, it is best to use regular expressions. You can also use the indexOf function to judge if it is greater than -1. {errMsg:"request:fail abort"...}
fail
abort

Sample code

  let reqTask = wx.request({
      url: getApp().globalData.api,
      success(res) {
        if (res.errMsg === "request:ok") console.log("res", res);
      },
      fail(err) {
        // if(err.errMsg.indexOf('request:fail')>-1) console.log('err', err);
        if (/^request:fail/i.test(err.errMsg)) console.log("err", err);
      },
      complete(res) {
        console.log("resOrErr", res);
      },
    });
   const reqTaskOnHeadersReceived = (headers) => {
      reqTask.offHeadersReceived(reqTaskOnHeadersReceived);
      console.log("headers", headers);
      // 由于请求还未完全结束,所以我们没办法获得请求的状态码,但是我们可以通过返回的requestBody的长度来进行判断。
      // 两点说明:1. 两个~~可以把字符串数字快速转化为数字。
      // 2. 为什么取小于19,是由于后台返回没有权限的requestBody的时候Content-length为“18”,正常情况下是大于19的。所以具体多少得看一下具体情况。
      if (~~headers.header["Content-length"] < 19) reqTask.abort();
    };
    reqTask.onHeadersReceived(reqTaskOnHeadersReceived);

Mini program login interface

  • ##wx .getUserProfile(Object object)

    Get user information. It can only be called after a click event is generated on the page (for example, in the callback of

    bindtap on button). An authorization window will pop up for each request, and userInfo will be returned after the user agrees. This interface is used to replace wx.getUserInfo. For details, see User Information Interface Adjustment Instructions.

  • wx.checkSession(Object object)

    Check whether the login status has expired. The user login status obtained through the wx.login interface has a certain timeliness. The longer a user has not used the mini program, the more likely it is that the user's login status will become invalid. On the other hand, if the user has been using the mini program, the user's login status will always remain valid. The specific timing logic is maintained by WeChat and is transparent to developers. Developers only need to call the wx.checkSession interface to check whether the current user login status is valid.

    After the login state expires, developers can call wx.login to obtain a new user login state. A successful call indicates that the current session_key has not expired, and a failed call indicates that the session_key has expired. For more usage methods, please see

    Mini Program Login.

  • wx.login(Object object)

    Call the interface to obtain the login credentials (code). The user's login status information is exchanged through the voucher, including the user's unique identification in the current mini program (openid), the unique identification under the WeChat open platform account (unionid, if the current mini program has been bound to the WeChat open platform account) and this login The session key (session_key), etc. Encryption and decryption of user data communication depends on the session key. For more usage methods, please see

    Mini Program Login.

Backend login interface code implementation

The backend uses NodeJS, web framework KOA version ^2.13 .4, routing framework @koa/router version ^10.1.1, framework request, version ^2.88.2, jsonwebtoken is used to encrypt and decrypt token information, version ^8.5.1

// app.js
const Koa = require("koa");
const Router = require("@koa/router");
const WeixinAuth = require("./lib/koa2-weixin-auth");
const jsonwebtoken = require("jsonwebtoken");

const app = new Koa();
// 小程序机票信息
const miniProgramAppId = "*********";
const miniProgramAppSecret = "***********";
const weixinAuth = new WeixinAuth(miniProgramAppId, miniProgramAppSecret);

const JWT_SECRET = "JWTSECRET";
// 路由中间件需要安装@koa/router
// 开启一个带群组的路由
const router = new Router({
  prefix: "/user",
});
// 这是正规的登陆方法
// 添加一个参数,sessionKeyIsValid,代表sessionKey是否还有效
router.post("/weixin-login", async (ctx) => {
  let { code, userInfo, encryptedData, iv, sessionKeyIsValid } =
    ctx.request.body;
   // 解析openid
  const token = await weixinAuth.getAccessToken(code);
  userInfo.openid = token.data.openid;
  // 这里可以自己进行处理,比方说记录到数据库,处理token等
  let authorizationToken = jsonwebtoken.sign(
    { name: userInfo.nickName },
    JWT_SECRET,
    { expiresIn: "1d" }
  );
  Object.assign(userInfo, { authorizationToken });
  ctx.status = 200;
  ctx.body = {
    code: 200,
    msg: "ok",
    data: userInfo,
  };
});
// lib/koa2-weixin-auth.js
const querystring = require("querystring");
const request = require("request");

const AccessToken = function (data) {
  if (!(this instanceof AccessToken)) {
    return new AccessToken(data);
  }
  this.data = data;
};

/*!
 * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比
 *
 * Examples:
 * ```
 * token.isValid();
 * ```
 */
AccessToken.prototype.isValid = function () {
  return (
    !!this.data.session_key &&
    new Date().getTime() < this.data.create_at + this.data.expires_in * 1000
  );
};

/**
 * 根据appid和appsecret创建OAuth接口的构造函数
 * 如需跨进程跨机器进行操作,access token需要进行全局维护
 * 使用使用token的优先级是:
 *
 * 1. 使用当前缓存的token对象
 * 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。

 * Examples:
 * ```
 * var OAuth = require(&#39;oauth&#39;);
 * var api = new OAuth(&#39;appid&#39;, &#39;secret&#39;);
 * ```
 * @param {String} appid 在公众平台上申请得到的appid
 * @param {String} appsecret 在公众平台上申请得到的app secret
 */
const Auth = function (appid, appsecret) {
  this.appid = appid;
  this.appsecret = appsecret;
  this.store = {};

  this.getToken = function (openid) {
    return this.store[openid];
  };

  this.saveToken = function (openid, token) {
    this.store[openid] = token;
  };
};

/**
 * 获取授权页面的URL地址
 * @param {String} redirect 授权后要跳转的地址
 * @param {String} state 开发者可提供的数据
 * @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转
 */
Auth.prototype.getAuthorizeURL = function (redirect_uri, scope, state) {
  return new Promise((resolve, reject) => {
    const url = "https://open.weixin.qq.com/connect/oauth2/authorize";
    let info = {
      appid: this.appid,
      redirect_uri: redirect_uri,
      scope: scope || "snsapi_base",
      state: state || "",
      response_type: "code",
    };
    resolve(url + "?" + querystring.stringify(info) + "#wechat_redirect");
  });
};

/*!
 * 处理token,更新过期时间
 */
Auth.prototype.processToken = function (data) {
  data.create_at = new Date().getTime();
  // 存储token
  this.saveToken(data.openid, data);
  return AccessToken(data);
};

/**
 * 根据授权获取到的code,换取access token和openid
 * 获取openid之后,可以调用`wechat.API`来获取更多信息
 * @param {String} code 授权获取到的code
 */
Auth.prototype.getAccessToken = function (code) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/jscode2session";
    //由于此框架版本很久没有更新了,此处地址发生了变化,需要修改为以上地址,不然会出现
    //41008错误。这也是没有直接使用框架,引用本地使用的原因。
    // const url = "https://api.weixin.qq.com/sns/oauth2/access_token";
    const info = {
      appid: this.appid,
      secret: this.appsecret,
      js_code: code,
      grant_type: "authorization_code",
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data));
      }
    });
  });
};

/**
 * 根据refresh token,刷新access token,调用getAccessToken后才有效
 * @param {String} refreshToken refreshToken
 */
Auth.prototype.refreshAccessToken = function (refreshToken) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
    var info = {
      appid: this.appid,
      grant_type: "refresh_token",
      refresh_token: refreshToken,
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data));
      }
    });
  });
};

/**
 * 根据openid,获取用户信息。
 * 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息
 * @param {Object|String} options 传入openid或者参见Options
 */
Auth.prototype.getUser = async function (openid) {
  const data = this.getToken(openid);
  console.log("getUser", data);
  if (!data) {
    var error = new Error(
      "No token for " + options.openid + ", please authorize first."
    );
    error.name = "NoOAuthTokenError";
    throw error;
  }
  const token = AccessToken(data);
  var accessToken;
  if (token.isValid()) {
    accessToken = token.data.session_key;
  } else {
    var newToken = await this.refreshAccessToken(token.data.refresh_token);
    accessToken = newToken.data.session_key;
  }
  console.log("accessToken", accessToken);
  return await this._getUser(openid, accessToken);
};

Auth.prototype._getUser = function (openid, accessToken, lang) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/userinfo";
    const info = {
      access_token: accessToken,
      openid: openid,
      lang: lang || "zh_CN",
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        resolve(JSON.parse(body));
      }
    });
  });
};

/**
 * 根据code,获取用户信息。
 * @param {String} code 授权获取到的code
 */
Auth.prototype.getUserByCode = async function (code) {
  const token = await this.getAccessToken(code);
  return await this.getUser(token.data.openid);
};

module.exports = Auth;

Mini program terminal login code implementation

<!--pages/index.wxml-->
<view class="page-section">
    <text class="page-section__title">微信登录</text>
    <view class="btn-area">
        <button  bindtap="getUserProfile" type="primary">登录</button>
    </view>
</view>
// pages/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {},
  // 正确的登录方式
  getUserProfile() {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        let { userInfo, encryptedData, iv } = res;
        const requestLoginApi = (code) => {
          // 发起网络请求
          wx.request({
            url: "http://localhost:3000/user/weixin-login",
            method: "POST",
            header: {
              "content-type": "application/json",
            },
            data: {
              code,
              userInfo,
              encryptedData,
              iv,
            },
            success(res) {
              console.log("请求成功", res.data);
              let token = res.data.data.authorizationToken;
              wx.setStorageSync("token", token);
              onUserLogin(token);
              console.log("authorization", token);
            },
            fail(err) {
              console.log("请求异常", err);
            },
          });
        };
        const onUserLogin = (token) => {
          getApp().globalData.token = token;
          wx.showToast({
            title: "登录成功了",
          });
        };
        //必须进行session是否过期检查,不然会出现第一次点击登录,服务器报Illegal Buffer
        //的错误,但是第二次点击登录正常。
        wx.checkSession({
          success: (res) => {
            // session_key 未过期,并且在本生命周期一直有效
            console.log("在登陆中");
            let token = wx.getStorageSync("token");
            if (token) onUserLogin(token);
          },
          fail: (res) => {
            // session_key已经失效,需要重新执行登录流程
            wx.login({
              success(res0) {
                if (res0.code) {
                  requestLoginApi(res0.code);
                } else {
                  console.log("登录失败!" + res0.errMsg);
                }
              },
            });
          },
        });
      },
    });
  },
});

What optimizations can be done for the login code?

For a software, at the code level, it is necessary to pursue the most basic aspects (far more than these, but let’s do these well first):

  • Maintainability (maintainability)

    The so-called "maintenance" is nothing more than work such as modifying bugs, modifying old code, and adding new code. The so-called "code is easy to maintain" means that the code can be quickly modified or added without destroying the original code design or introducing new bugs. The so-called "code is not easy to maintain" means that modifying or adding code requires a great risk of introducing new bugs and takes a long time to complete.

  • Readability (readability)

    Software design guru Martin Fowler once said: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Translated into Chinese: "Any fool can write code that computers can understand. Good programmers can write code that humans can understand." There is even a certification within Google called Readability. Only engineers who have obtained this certification are qualified to approve others to submit code during code review. It can be seen how important the readability of the code is. After all, the number of times the code is read far exceeds the number of times it is written and executed. We need to check whether the code complies with coding standards, whether the naming is meaningful, whether the comments are detailed, whether the function length is appropriate, whether the module division is clear, whether it meets high cohesion and low coupling, etc.

  • Extensibility (extensibility)

    Extensibility is also a very important criterion for evaluating code quality. The code reserves some function extension points. You can insert new function code directly into the extension points without having to go to great lengths and change a large amount of original code just to add a function.

  • Reusability

    代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码。

那么接下来就来优化一下代码吧:

模块化

可以把登录的代码模块化,代码如下:

// lib/login.js
function loginWithCallback(cb) {
  // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
  // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
  wx.getUserProfile({
    desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
    success: (res) => {
      let { userInfo, encryptedData, iv } = res;
      const requestLoginApi = (code) => {
        // 发起网络请求
        wx.request({
          url: "http://localhost:3000/user/weixin-login",
          method: "POST",
          header: {
            "content-type": "application/json",
          },
          data: {
            code,
            userInfo,
            encryptedData,
            iv,
          },
          success(res) {
            console.log("请求成功", res.data);
            let token = res.data.data.authorizationToken;
            wx.setStorageSync("token", token);
            onUserLogin(token);
            console.log("authorization", token);
          },
          fail(err) {
            console.log("请求异常", err);
          },
        });
      };

      const onUserLogin = (token) => {
        getApp().globalData.token = token;
        wx.showToast({
          title: "登录成功了",
        });
        if (cb && typeof cb == "function") cb(token);
      };
      wx.checkSession({
        success: (res) => {
          // session_key 未过期,并且在本生命周期一直有效
          console.log("在登陆中");
          let token = wx.getStorageSync("token");
          if (token) onUserLogin(token);
        },
        fail: (res) => {
          // session_key已经失效,需要重新执行登录流程
          wx.login({
            success(res0) {
              if (res0.code) {
                requestLoginApi(res0.code);
              } else {
                console.log("登录失败!" + res0.errMsg);
              }
            },
          });
        },
      });
    },
  });
}

export default loginWithCallback;

Promise化

回调地狱问题,不利于代码的阅读,所以接下来我们基于Promise进行代码优化。有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise的几个方法简介

The method returns a new Promise object, so it can be written in a chained manner. This design allows nested asynchronous operations to be easily rewritten, from "horizontal development" of callback functions to "downward development". is an alias for Promise.prototype.then(null, rejection), used to specify when an error occurs Callback. Errors on Promise objects have a "bubble" nature and will be propagated backward until they are caught. That is, the error will always be caught by the next catch statement. method returns a This avoids the need for the same statement in The Promise.race method also wraps multiple Promise instances into a new Promise instance. Receives a Promise iterable object, and as long as one of the promises succeeds, the successful promise will be returned. All sub-instances are in the rejected state, and the total promise is in the rejected state. Returns a value when all given promises have been

小程序API接口Promise化并且把需要登录的调用接口模块化

1、安装插件。请先查看npm支持文档。

npm install --save miniprogram-api-promise

2、在微信开发者工具右方详情中勾选使用npm模块,并在菜单栏工具中点击构建npm。

3、初始化代码。

// app.js
import {promisifyAll} from &#39;miniprogram-api-promise&#39;
import login from "../lib/login";
const wxp ={}
promisifyAll(wx,wxp)
// 需要token的请求统一处理登录和设置header,并且处理错误信息
wxp.requestNeedLogin = async function (args) {
  let token = wx.getStorageSync("token");
  if (!token) {
    token = await loginWithPromise();
  }
  if (!args.header) args.header = {};
  args.header["Authorization"] = `Bearer ${token}`;
  return wxp.request(args).catch(console.error);
};
// app.js
App({
  wxp:wxp,
});

4、改写login.js代码

// lib/login.js
function login() {
  return new Promise((resolve, reject) => {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
       success:async (res0) => {
        let {
          userInfo,
          encryptedData,
          iv
        } = res0;
        const app = getApp();
        try {
          app.wxp.checkSession();
        } catch (err) {
          reject(err);
        }
        let token = wx.getStorageSync("token");
        if (!token) {
          let res1 = await app.wxp.login().catch(err => reject(err));
          let code = res1.code;
          let res = await app.wxp.request({
            url: "http://localhost:3000/user/weixin-login",
            method: "POST",
            header: {
              "content-type": "application/json",
            },
            data: {
              code,
              userInfo,
              encryptedData,
              iv,
            }
          }).catch(err => reject(err));
          token = res.data.data.authorizationToken;
          wx.setStorageSync("token", token);
          app.globalData.token = token;
          wx.showToast({
            title: "登录成功了",
          });
          resolve(token);
        }
      },
    });
  })
}

export default login;

5、调用代码

<view class="container page-head">
  <text class="page-section__title">需要登录的请求调用</text>
  <view class="btn-area">
    <button bindtap="request1" type="primary">请求1</button>
    <button bindtap="request2" type="primary">请求2</button>
  </view>
</view>
// pages/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {},
  request1() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home?name=andying",
      }).then(console.log)
  },
  request2() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home?name=eva",
      }).then(console.log)
  },
});

【相关学习推荐:小程序开发教程

Method name Description
##Promise.prototype.then
Promise.prototype.catch
Promise.prototype.finally Promise. At the end of the promise, regardless of whether the result is fulfilled or rejected, the specified callback function will be executed. This provides a way for code that needs to be executed whether the Promise completes successfully or not.
Promise.all then() and catch() are written once each. The Promise.all method is used to wrap multiple Promise instances into a new Promise instance. The Promise.all method accepts an array as a parameter, var p = Promise.all([p1,p2,p3]);p1, p2, p3 are all instances of the Promise object. (The parameters of the Promise.all method are not necessarily arrays, but they must have an iterator interface, and each member returned is a Promise instance.) The state of p is determined by p1, p2, and p3, and is divided into two situations. (1) Only when the status of p1, p2, and p3 all become fulfilled, the status of p will become fulfilled. At this time, the return values ​​of p1, p2, and p3 form an array and are passed to the callback function of p. (2) As long as one of p1, p2, and p3 is rejected, the status of p becomes rejected. At this time, the return value of the first rejected instance will be passed to the callback function of p.
Promise.race var p = Promise.race([p1,p2,p3]);In the above code, as long as one instance among p1, p2, and p3 changes the state first, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the return value of p.
Promise.any
Promise.allSettled fulfilled or rejected# The promise after ## is accompanied by an array of objects, each object representing the corresponding promise result. In contrast, Promise.all() is more suitable for dependencies on each other or ending immediately when any of them reject.

The above is the detailed content of A brief analysis of how to implement the login function in mini programs. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete